diff --git a/apps/desktop/com.bitwarden.pfx b/apps/desktop/com.bitwarden.pfx
new file mode 100644
index 00000000000..ed82d494b20
Binary files /dev/null and b/apps/desktop/com.bitwarden.pfx differ
diff --git a/apps/desktop/custom-appx-manifest.xml b/apps/desktop/custom-appx-manifest.xml
index 44ad4c2eaea..c108e060e9d 100644
--- a/apps/desktop/custom-appx-manifest.xml
+++ b/apps/desktop/custom-appx-manifest.xml
@@ -13,10 +13,10 @@ xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
IgnorableNamespaces="uap rescap com uap10 build"
xmlns:build="http://schemas.microsoft.com/developer/appx/2015/build">
-
+
Bitwarden
Bitwarden Inc
diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock
index f7081f056ae..674c0e40a3b 100644
--- a/apps/desktop/desktop_native/Cargo.lock
+++ b/apps/desktop/desktop_native/Cargo.lock
@@ -581,6 +581,33 @@ dependencies = [
"cpufeatures",
]
+[[package]]
+name = "ciborium"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
+dependencies = [
+ "ciborium-io",
+ "ciborium-ll",
+ "serde",
+]
+
+[[package]]
+name = "ciborium-io"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
+
+[[package]]
+name = "ciborium-ll"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
+dependencies = [
+ "ciborium-io",
+ "half",
+]
+
[[package]]
name = "cipher"
version = "0.4.4"
@@ -723,6 +750,12 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+[[package]]
+name = "crunchy"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
+
[[package]]
name = "crypto-common"
version = "0.1.6"
@@ -1401,6 +1434,16 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "half"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+]
+
[[package]]
name = "hashbrown"
version = "0.14.5"
@@ -4547,9 +4590,13 @@ checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
name = "windows_plugin_authenticator"
version = "0.0.0"
dependencies = [
+ "ciborium",
"hex",
"reqwest",
+ "serde",
"serde_json",
+ "sha2",
+ "tokio",
"windows 0.61.1 (git+https://github.com/microsoft/windows-rs.git?rev=d09b4681de02560cf05bd3e57d7ea56b73f3b2f8)",
"windows-core 0.61.2 (git+https://github.com/microsoft/windows-rs.git?rev=d09b4681de02560cf05bd3e57d7ea56b73f3b2f8)",
]
diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts
index b3c6f715e98..b973c19d3b1 100644
--- a/apps/desktop/desktop_native/napi/index.d.ts
+++ b/apps/desktop/desktop_native/napi/index.d.ts
@@ -3,6 +3,10 @@
/* auto-generated by NAPI-RS */
+export interface PasskeyRequestEvent {
+ requestType: string
+ requestJson: string
+}
export declare namespace passwords {
/** Fetch the stored password from the keychain. */
export function getPassword(service: string, account: string): Promise
@@ -186,7 +190,51 @@ export declare namespace crypto {
export function argon2(secret: Buffer, salt: Buffer, iterations: number, memory: number, parallelism: number): Promise
}
export declare namespace passkey_authenticator {
+ export interface SyncedCredential {
+ credentialId: string
+ rpId: string
+ userName: string
+ userId: string
+ }
+ export interface PasskeyAssertionRequest {
+ rpId: string
+ transactionId: string
+ clientDataHash: Array
+ allowedCredentials: Array>
+ userVerification: boolean
+ }
+ export interface PasskeyRegistrationRequest {
+ rpId: string
+ transactionId: string
+ userId: Array
+ userName: string
+ clientDataHash: Array
+ userVerification: boolean
+ supportedAlgorithms: Array
+ }
+ export interface PasskeySyncRequest {
+ rpId: string
+ }
+ export interface PasskeyAssertionResponse {
+ credentialId: Array
+ authenticatorData: Array
+ signature: Array
+ userHandle: Array
+ }
+ export interface PasskeyRegistrationResponse {
+ credentialId: Array
+ attestationObject: Array
+ }
+ export interface PasskeySyncResponse {
+ credentials: Array
+ }
+ export interface PasskeyErrorResponse {
+ message: string
+ }
export function register(): void
+ export function onRequest(callback: (error: null | Error, event: PasskeyRequestEvent) => Promise): Promise
+ export function syncCredentialsToWindows(credentials: Array): void
+ export function getCredentialsFromWindows(): Array
}
export declare namespace logging {
export const enum LogLevel {
diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs
index 079872a3b03..95ec030fbc3 100644
--- a/apps/desktop/desktop_native/napi/src/lib.rs
+++ b/apps/desktop/desktop_native/napi/src/lib.rs
@@ -810,12 +810,213 @@ pub mod crypto {
#[napi]
pub mod passkey_authenticator {
+ use napi::threadsafe_function::{
+ ErrorStrategy::CalleeHandled, ThreadsafeFunction,
+ };
+ use serde_json;
+
+ // Re-export the platform-specific types
+ pub use crate::passkey_authenticator_internal::PasskeyRequestEvent;
+
+ #[napi(object)]
+ #[derive(serde::Serialize, serde::Deserialize)]
+ pub struct SyncedCredential {
+ pub credential_id: String, // base64url encoded
+ pub rp_id: String,
+ pub user_name: String,
+ pub user_id: String, // base64url encoded
+ }
+
+ impl From for SyncedCredential {
+ fn from(cred: windows_plugin_authenticator::SyncedCredential) -> Self {
+ use base64::Engine;
+ Self {
+ credential_id: base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&cred.credential_id),
+ rp_id: cred.rp_id,
+ user_name: cred.user_name,
+ user_id: base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&cred.user_id),
+ }
+ }
+ }
+
+ impl From for windows_plugin_authenticator::SyncedCredential {
+ fn from(cred: SyncedCredential) -> Self {
+ use base64::Engine;
+ Self {
+ credential_id: base64::engine::general_purpose::URL_SAFE_NO_PAD
+ .decode(&cred.credential_id)
+ .unwrap_or_default(),
+ rp_id: cred.rp_id,
+ user_name: cred.user_name,
+ user_id: base64::engine::general_purpose::URL_SAFE_NO_PAD
+ .decode(&cred.user_id)
+ .unwrap_or_default(),
+ }
+ }
+ }
+
+ #[napi(object)]
+ #[derive(serde::Serialize, serde::Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct PasskeyAssertionRequest {
+ pub rp_id: String,
+ pub transaction_id: String,
+ pub client_data_hash: Vec,
+ pub allowed_credentials: Vec>,
+ pub user_verification: bool,
+ }
+
+ #[napi(object)]
+ #[derive(serde::Serialize, serde::Deserialize)]
+ #[serde(rename_all = "camelCase")]
+
+ pub struct PasskeyRegistrationRequest {
+ pub rp_id: String,
+ pub transaction_id: String,
+ pub user_id: Vec,
+ pub user_name: String,
+ pub client_data_hash: Vec,
+ pub user_verification: bool,
+ pub supported_algorithms: Vec,
+ }
+
+ #[napi(object)]
+ #[derive(serde::Serialize, serde::Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct PasskeySyncRequest {
+ pub rp_id: String,
+ }
+
+ #[napi(object)]
+ #[derive(serde::Serialize, serde::Deserialize)]
+ #[serde(rename_all = "camelCase")]
+
+ pub struct PasskeyAssertionResponse {
+ pub credential_id: Vec,
+ pub authenticator_data: Vec,
+ pub signature: Vec,
+ pub user_handle: Vec,
+ }
+
+ #[napi(object)]
+ #[derive(serde::Serialize, serde::Deserialize)]
+ #[serde(rename_all = "camelCase")]
+
+ pub struct PasskeyRegistrationResponse {
+ pub credential_id: Vec,
+ pub attestation_object: Vec,
+ }
+
+ #[napi(object)]
+ #[derive(serde::Serialize, serde::Deserialize)]
+ #[serde(rename_all = "camelCase")]
+
+ pub struct PasskeySyncResponse {
+ pub credentials: Vec,
+ }
+
+ #[napi(object)]
+ #[derive(serde::Serialize, serde::Deserialize)]
+ #[serde(rename_all = "camelCase")]
+
+ pub struct PasskeyErrorResponse {
+ pub message: String,
+ }
+
+
#[napi]
pub fn register() -> napi::Result<()> {
crate::passkey_authenticator_internal::register().map_err(|e| {
napi::Error::from_reason(format!("Passkey registration failed - Error: {e} - {e:?}"))
})
}
+
+ #[napi]
+ pub async fn on_request(
+ #[napi(ts_arg_type = "(error: null | Error, event: PasskeyRequestEvent) => Promise")]
+ callback: ThreadsafeFunction,
+ ) -> napi::Result {
+ crate::passkey_authenticator_internal::on_request(callback).await
+ }
+
+ #[napi]
+ pub fn sync_credentials_to_windows(credentials: Vec) -> napi::Result<()> {
+ const PLUGIN_CLSID: &str = "0f7dc5d9-69ce-4652-8572-6877fd695062";
+
+ log::info!("[NAPI] sync_credentials_to_windows called with {} credentials", credentials.len());
+
+ // Log each credential being synced (with truncated IDs for security)
+ for (i, cred) in credentials.iter().enumerate() {
+ let truncated_cred_id = if cred.credential_id.len() > 16 {
+ format!("{}...", &cred.credential_id[..16])
+ } else {
+ cred.credential_id.clone()
+ };
+ let truncated_user_id = if cred.user_id.len() > 16 {
+ format!("{}...", &cred.user_id[..16])
+ } else {
+ cred.user_id.clone()
+ };
+ log::info!("[NAPI] Credential {}: RP={}, User={}, CredID={}, UserID={}",
+ i + 1, cred.rp_id, cred.user_name, truncated_cred_id, truncated_user_id);
+ }
+
+ // Convert NAPI types to internal types using From trait
+ let internal_credentials: Vec = credentials
+ .into_iter()
+ .map(|cred| cred.into())
+ .collect();
+
+ log::info!("[NAPI] Calling Windows Plugin Authenticator sync with CLSID: {}", PLUGIN_CLSID);
+ let result = windows_plugin_authenticator::sync_credentials_to_windows(internal_credentials, PLUGIN_CLSID);
+
+ match &result {
+ Ok(()) => log::info!("[NAPI] sync_credentials_to_windows completed successfully"),
+ Err(e) => log::error!("[NAPI] sync_credentials_to_windows failed: {}", e),
+ }
+
+ result.map_err(|e| napi::Error::from_reason(format!("Sync credentials failed: {}", e)))
+ }
+
+ #[napi]
+ pub fn get_credentials_from_windows() -> napi::Result> {
+ const PLUGIN_CLSID: &str = "0f7dc5d9-69ce-4652-8572-6877fd695062";
+
+ log::info!("[NAPI] get_credentials_from_windows called with CLSID: {}", PLUGIN_CLSID);
+
+ let result = windows_plugin_authenticator::get_credentials_from_windows(PLUGIN_CLSID);
+
+ let internal_credentials = match &result {
+ Ok(creds) => {
+ log::info!("[NAPI] Retrieved {} credentials from Windows", creds.len());
+ result.map_err(|e| napi::Error::from_reason(format!("Get credentials failed: {}", e)))?
+ }
+ Err(e) => {
+ log::error!("[NAPI] get_credentials_from_windows failed: {}", e);
+ return Err(napi::Error::from_reason(format!("Get credentials failed: {}", e)));
+ }
+ };
+
+ // Convert internal types to NAPI types using From trait
+ let napi_credentials: Vec = internal_credentials
+ .into_iter()
+ .enumerate()
+ .map(|(i, cred)| {
+ let result_cred: SyncedCredential = cred.into();
+ let truncated_cred_id = if result_cred.credential_id.len() > 16 {
+ format!("{}...", &result_cred.credential_id[..16])
+ } else {
+ result_cred.credential_id.clone()
+ };
+ log::info!("[NAPI] Retrieved credential {}: RP={}, User={}, CredID={}",
+ i + 1, result_cred.rp_id, result_cred.user_name, truncated_cred_id);
+ result_cred
+ })
+ .collect();
+
+ log::info!("[NAPI] get_credentials_from_windows completed successfully, returning {} credentials", napi_credentials.len());
+ Ok(napi_credentials)
+ }
}
#[napi]
diff --git a/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/dummy.rs b/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/dummy.rs
index bcd929c16b4..9cd748ba185 100644
--- a/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/dummy.rs
+++ b/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/dummy.rs
@@ -1,5 +1,22 @@
use anyhow::{bail, Result};
+use napi::threadsafe_function::{
+ ErrorStrategy::CalleeHandled, ThreadsafeFunction,
+};
+
+#[napi(object)]
+#[derive(Debug)]
+pub struct PasskeyRequestEvent {
+ pub operation: String,
+ pub rpid: String,
+ pub transaction_id: String,
+}
pub fn register() -> Result<()> {
bail!("Not implemented")
}
+
+pub async fn on_request(
+ _callback: ThreadsafeFunction,
+) -> napi::Result {
+ Err(napi::Error::from_reason("Passkey authenticator is not supported on this platform".to_string()))
+}
diff --git a/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/windows.rs b/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/windows.rs
index 4ff51f5bce4..13bc8f71b32 100644
--- a/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/windows.rs
+++ b/apps/desktop/desktop_native/napi/src/passkey_authenticator_internal/windows.rs
@@ -1,7 +1,82 @@
use anyhow::{anyhow, Result};
+use napi::{bindgen_prelude::Promise, threadsafe_function::{
+ ErrorStrategy::CalleeHandled, ThreadsafeFunction, ThreadsafeFunctionCallMode,
+}, JsObject};
+use tokio::sync::mpsc;
+use serde_json;
+use windows_plugin_authenticator::util;
+
+// Simple wrapper for passing JSON strings to TypeScript
+#[napi(object)]
+#[derive(Debug)]
+pub struct PasskeyRequestEvent {
+ pub request_type: String,
+ pub request_json: String,
+}
pub fn register() -> Result<()> {
windows_plugin_authenticator::register().map_err(|e| anyhow!(e))?;
Ok(())
}
+
+pub async fn on_request(
+ callback: ThreadsafeFunction,
+) -> napi::Result {
+ let (tx, mut rx) = mpsc::unbounded_channel();
+
+ // Set the sender in the Windows plugin authenticator
+ windows_plugin_authenticator::set_request_sender(tx);
+
+ // Spawn task to handle incoming events
+ tokio::spawn(async move {
+ while let Some(event) = rx.recv().await {
+ // The request is already serialized as JSON in the event
+ let request_json = event.request_json;
+
+ // Get the request type as a string
+ let request_type = match event.request_type {
+ windows_plugin_authenticator::RequestType::Assertion => "assertion".to_string(),
+ windows_plugin_authenticator::RequestType::Registration => "registration".to_string(),
+ windows_plugin_authenticator::RequestType::Sync => "sync".to_string(),
+ };
+
+ let napi_event = PasskeyRequestEvent { request_type, request_json };
+
+ // Call the callback asynchronously and capture the return value
+ let promise_result: Result, napi::Error> = callback.call_async(Ok(napi_event)).await;
+ // awai promse
+
+ match promise_result {
+ Ok(promise_result) => match promise_result.await {
+ Ok(result) => {
+ util::message(&format!("CALLBACK COMPLETED WITH RESPONSE: {}", result));
+ // Parse the JSON response directly back to Rust enum
+ let response: windows_plugin_authenticator::PasskeyResponse = match serde_json::from_str(&result) {
+ Ok(resp) => resp,
+ Err(e) => windows_plugin_authenticator::PasskeyResponse::Error {
+ message: format!("JSON parse error: {}", e),
+ }
+ };
+ let _ = event.response_sender.send(response);
+ }
+ Err(e) => {
+ eprintln!("Error calling passkey callback inner: {}", e);
+ let _ = event.response_sender.send(windows_plugin_authenticator::PasskeyResponse::Error {
+ message: format!("Inner Callback error: {}", e),
+ });
+
+ }
+ }
+ Err(e) => {
+ eprintln!("Error calling passkey callback: {}", e);
+ let _ = event.response_sender.send(windows_plugin_authenticator::PasskeyResponse::Error {
+ message: format!("Callback error: {}", e),
+ });
+ }
+ }
+ }
+ });
+
+ Ok("Event listener registered successfully".to_string())
+}
diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/Cargo.toml b/apps/desktop/desktop_native/windows_plugin_authenticator/Cargo.toml
index 1c6dee6abfb..1631d430188 100644
--- a/apps/desktop/desktop_native/windows_plugin_authenticator/Cargo.toml
+++ b/apps/desktop/desktop_native/windows_plugin_authenticator/Cargo.toml
@@ -11,3 +11,7 @@ windows-core = { workspace = true }
hex = { workspace = true }
reqwest = { version = "0.12", features = ["json", "blocking"] }
serde_json = { workspace = true }
+serde = { workspace = true, features = ["derive"] }
+ciborium = "0.2"
+sha2 = "0.10"
+tokio = { workspace = true }
diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/PluginAuthenticatorImpl.cpp.sample b/apps/desktop/desktop_native/windows_plugin_authenticator/src/PluginAuthenticatorImpl.cpp.sample
new file mode 100644
index 00000000000..21025834182
--- /dev/null
+++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/PluginAuthenticatorImpl.cpp.sample
@@ -0,0 +1,977 @@
+#include "pch.h"
+#include "PluginAuthenticatorImpl.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+namespace winrt
+{
+ using namespace winrt::Windows::Foundation;
+ using namespace winrt::Microsoft::UI::Windowing;
+ using namespace winrt::Microsoft::UI::Xaml;
+ using namespace winrt::Microsoft::UI::Xaml::Controls;
+ using namespace winrt::Microsoft::UI::Xaml::Navigation;
+ using namespace PasskeyManager;
+ using namespace PasskeyManager::implementation;
+ using namespace CborLite;
+}
+
+namespace winrt::PasskeyManager::implementation
+{
+ static std::vector GetRequestSigningPubKey()
+ {
+ return wil::reg::get_value_binary(HKEY_CURRENT_USER, c_pluginRegistryPath, c_windowsPluginRequestSigningKeyRegKeyName, REG_BINARY);
+ }
+
+ /*
+ * This function is used to verify the signature of a request buffer.
+ * The public key is part of response to plugin registration.
+ */
+ static HRESULT VerifySignatureHelper(
+ std::vector& dataBuffer,
+ PBYTE pbKeyData,
+ DWORD cbKeyData,
+ PBYTE pbSignature,
+ DWORD cbSignature)
+ {
+ // Create key provider
+ wil::unique_ncrypt_prov hProvider;
+ wil::unique_ncrypt_key reqSigningKey;
+
+ // Get the provider
+ RETURN_IF_FAILED(NCryptOpenStorageProvider(&hProvider, nullptr, 0));
+ // Create a NCrypt key handle from the public key
+ RETURN_IF_FAILED(NCryptImportKey(
+ hProvider.get(),
+ NULL,
+ BCRYPT_ECCPUBLIC_BLOB,
+ NULL,
+ &reqSigningKey,
+ pbKeyData,
+ cbKeyData, 0));
+
+ // Verify the signature over the hash of dataBuffer using the hKey
+ DWORD objLenSize = 0;
+ DWORD bytesRead = 0;
+ RETURN_IF_NTSTATUS_FAILED(BCryptGetProperty(
+ BCRYPT_SHA256_ALG_HANDLE,
+ BCRYPT_OBJECT_LENGTH,
+ reinterpret_cast(&objLenSize),
+ sizeof(objLenSize),
+ &bytesRead, 0));
+
+ auto objLen = wil::make_unique_cotaskmem(objLenSize);
+ wil::unique_bcrypt_hash hashHandle;
+ RETURN_IF_NTSTATUS_FAILED(BCryptCreateHash(
+ BCRYPT_SHA256_ALG_HANDLE,
+ wil::out_param(hashHandle),
+ objLen.get(),
+ objLenSize,
+ nullptr, 0, 0));
+ RETURN_IF_NTSTATUS_FAILED(BCryptHashData(
+ hashHandle.get(),
+ dataBuffer.data(),
+ static_cast(dataBuffer.size()), 0));
+
+ DWORD localHashByteCount = 0;
+ RETURN_IF_NTSTATUS_FAILED(BCryptGetProperty(
+ BCRYPT_SHA256_ALG_HANDLE,
+ BCRYPT_HASH_LENGTH,
+ reinterpret_cast(&localHashByteCount),
+ sizeof(localHashByteCount),
+ &bytesRead, 0));
+
+ auto localHashBuffer = wil::make_unique_cotaskmem(localHashByteCount);
+ RETURN_IF_NTSTATUS_FAILED(BCryptFinishHash(hashHandle.get(), localHashBuffer.get(), localHashByteCount, 0));
+ RETURN_IF_WIN32_ERROR(NCryptVerifySignature(
+ reqSigningKey.get(),
+ nullptr,
+ localHashBuffer.get(),
+ localHashByteCount,
+ pbSignature,
+ cbSignature, 0));
+
+ return S_OK;
+ }
+
+ HRESULT CheckHelloConsentCompleted()
+ {
+ winrt::com_ptr curApp = winrt::Microsoft::UI::Xaml::Application::Current().as();
+ HANDLE handles[2] = { curApp->m_hVaultConsentComplete.get(), curApp->m_hVaultConsentFailed.get() };
+
+ DWORD cWait = ARRAYSIZE(handles);
+ DWORD hIndex = 0;
+ RETURN_IF_FAILED(CoWaitForMultipleHandles(COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS, INFINITE, cWait, handles, &hIndex));
+ if (hIndex == 1) // Consent failed
+ {
+ RETURN_HR(E_FAIL);
+ }
+ return S_OK;
+ }
+
+ HRESULT PerformUv(
+ winrt::com_ptr& curApp,
+ HWND hWnd,
+ wil::shared_hmodule webauthnDll,
+ GUID transactionId,
+ PluginOperationType operationType,
+ std::vector requestBuffer,
+ wil::shared_cotaskmem_string rpName,
+ wil::shared_cotaskmem_string userName)
+ {
+ curApp->SetPluginPerformOperationOptions(hWnd, operationType, rpName.get(), userName.get());
+
+ // Wait for the app main window to be ready.
+ DWORD hIndex = 0;
+ RETURN_IF_FAILED(CoWaitForMultipleHandles(COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS, INFINITE, 1, curApp->m_hWindowReady.addressof(), &hIndex));
+
+ // Trigger a Consent Verifier Dialog to simulate a Windows Hello unlock flow
+ // This is to demonstrate a vault unlock flow using Windows Hello and is not the recommended way to secure the vault
+ if (PluginCredentialManager::getInstance().GetVaultLock())
+ {
+ curApp->GetDispatcherQueue().TryEnqueue([curApp]()
+ {
+ curApp->SimulateUnLockVaultUsingConsentVerifier();
+ });
+ RETURN_IF_FAILED(CheckHelloConsentCompleted());
+ }
+ else
+ {
+ SetEvent(curApp->m_hVaultConsentComplete.get());
+ }
+
+ // Wait for user confirmation to proceed with the operation Create/Signin/Cancel button
+ // This is a mock up for plugin requiring UI.
+ {
+ HANDLE handles[2] = { curApp->m_hPluginProceedButtonEvent.get(), curApp->m_hPluginUserCancelEvent.get() };
+ DWORD cWait = ARRAYSIZE(handles);
+
+ RETURN_IF_FAILED(CoWaitForMultipleHandles(COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS, INFINITE, cWait, handles, &hIndex));
+ if (hIndex == 1) // Cancel button clicked
+ {
+ // User cancelled the operation. NTE_USER_CANCELLED allows Windows to distinguish between user cancellation and other errors.
+ return NTE_USER_CANCELLED;
+ }
+ }
+
+ // Skip user verification if the user has already performed a gesture to unlock the vault to avoid double prompting
+ if (PluginCredentialManager::getInstance().GetVaultLock())
+ {
+ return S_OK;
+ }
+
+ EXPERIMENTAL_WEBAUTHN_PLUGIN_PERFORM_UV pluginPerformUv{};
+ pluginPerformUv.transactionId = &transactionId;
+
+ if (curApp->m_silentMode)
+ {
+ // If the app did not display any UI, use the hwnd of the caller here. This was included in the request to the plugin. Refer: EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST
+ pluginPerformUv.hwnd = hWnd;
+ }
+ else
+ {
+ // If the app displayed UI, use the hwnd of the app window here
+ pluginPerformUv.hwnd = curApp->GetNativeWindowHandle();
+ }
+
+ EXPERIMENTAL_PWEBAUTHN_PLUGIN_PERFORM_UV_RESPONSE pPluginPerformUvResponse = nullptr;
+
+ auto webAuthNPluginPerformUv = GetProcAddressByFunctionDeclaration(webauthnDll.get(), EXPERIMENTAL_WebAuthNPluginPerformUv);
+ RETURN_HR_IF_NULL(E_NOTIMPL, webAuthNPluginPerformUv);
+
+ // Step 1: Get the UV count
+ pluginPerformUv.type = EXPERIMENTAL_WEBAUTHN_PLUGIN_PERFORM_UV_OPERATION_TYPE::GetUvCount;
+ RETURN_IF_FAILED(webAuthNPluginPerformUv(&pluginPerformUv, &pPluginPerformUvResponse));
+
+ /*
+ * pPluginPerformUvResponse->pbResponse contains the UV count
+ * The UV count tracks the number of times the user has performed a gesture to unlock the vault
+ */
+
+ // Step 2: Get the public key
+ pluginPerformUv.type = EXPERIMENTAL_WEBAUTHN_PLUGIN_PERFORM_UV_OPERATION_TYPE::GetPubKey;
+ RETURN_IF_FAILED(webAuthNPluginPerformUv(&pluginPerformUv, &pPluginPerformUvResponse));
+
+ // stash public key in a new buffer for later use
+ DWORD cbPubData = pPluginPerformUvResponse->cbResponse;
+ wil::unique_hlocal_ptr ppbPubKeyData = wil::make_unique_hlocal(cbPubData);
+ memcpy_s(ppbPubKeyData.get(), cbPubData, pPluginPerformUvResponse->pbResponse, pPluginPerformUvResponse->cbResponse);
+
+ // Step 3: Perform UV. This step uses a Windows Hello prompt to authenticate the user
+ pluginPerformUv.type = EXPERIMENTAL_WEBAUTHN_PLUGIN_PERFORM_UV_OPERATION_TYPE::PerformUv;
+ pluginPerformUv.pwszUsername = wil::make_cotaskmem_string(userName.get()).release();
+ // pwszContext can be used to provide additional context to the user. This is displayed alongside the username in the Windows Hello passkey user verification dialog.
+ pluginPerformUv.pwszContext = wil::make_cotaskmem_string(L"Context String").release();
+ RETURN_IF_FAILED(webAuthNPluginPerformUv(&pluginPerformUv, &pPluginPerformUvResponse));
+
+ // Verify the signature over the hash of requestBuffer using the hKey
+ auto signatureVerifyResult = VerifySignatureHelper(
+ requestBuffer,
+ ppbPubKeyData.get(),
+ cbPubData,
+ pPluginPerformUvResponse->pbResponse,
+ pPluginPerformUvResponse->cbResponse);
+ curApp->GetDispatcherQueue().TryEnqueue([curApp, signatureVerifyResult]()
+ {
+ if (FAILED(signatureVerifyResult))
+ {
+ curApp->m_pluginOperationStatus.uvSignatureVerificationStatus = signatureVerifyResult;
+ }
+ });
+ return S_OK;
+ }
+
+ /*
+ * This function is used to create a simplified version of authenticator data for the webauthn authenticator operations.
+ * Refer: https://www.w3.org/TR/webauthn-3/#authenticator-data for more details.
+ */
+ HRESULT CreateAuthenticatorData(wil::shared_ncrypt_key hKey,
+ DWORD cbRpId,
+ PBYTE pbRpId,
+ DWORD& pcbPackedAuthenticatorData,
+ wil::unique_hlocal_ptr& ppbpackedAuthenticatorData,
+ std::vector& vCredentialIdBuffer)
+ {
+ // Get the public key blob
+ DWORD cbPubKeyBlob = 0;
+ THROW_IF_FAILED(NCryptExportKey(
+ hKey.get(),
+ NULL,
+ BCRYPT_ECCPUBLIC_BLOB,
+ NULL,
+ NULL,
+ 0,
+ &cbPubKeyBlob,
+ 0));
+ auto pbPubKeyBlob = std::make_unique(cbPubKeyBlob);
+ THROW_HR_IF(E_UNEXPECTED, pbPubKeyBlob == nullptr);
+ DWORD cbPubKeyBlobOutput = 0;
+ THROW_IF_FAILED(NCryptExportKey(
+ hKey.get(),
+ NULL,
+ BCRYPT_ECCPUBLIC_BLOB,
+ NULL,
+ pbPubKeyBlob.get(),
+ cbPubKeyBlob,
+ &cbPubKeyBlobOutput,
+ 0));
+
+ BCRYPT_ECCKEY_BLOB* pPubKeyBlobHeader = reinterpret_cast(pbPubKeyBlob.get());
+ DWORD cbXCoord = pPubKeyBlobHeader->cbKey;
+ PBYTE pbXCoord = reinterpret_cast(&pPubKeyBlobHeader[1]);
+ DWORD cbYCoord = pPubKeyBlobHeader->cbKey;
+ PBYTE pbYCoord = pbXCoord + cbXCoord;
+
+ // create byte span for x and y
+ std::span xCoord(pbXCoord, cbXCoord);
+ std::span yCoord(pbYCoord, cbYCoord);
+
+ // CBOR encode the public key in this order: kty, alg, crv, x, y
+ std::vector buffer;
+
+#pragma warning(push)
+#pragma warning(disable: 4293)
+ size_t bufferSize = CborLite::encodeMapSize(buffer, 5u);
+#pragma warning(pop)
+
+ // COSE CBOR encoding format. Refer to https://datatracker.ietf.org/doc/html/rfc9052#section-7 for more details.
+ const int8_t ktyIndex = 1;
+ const int8_t algIndex = 3;
+ const int8_t crvIndex = -1;
+ const int8_t xIndex = -2;
+ const int8_t yIndex = -3;
+
+ // Example values for EC2 P-256 ES256 Keys. Refer to https://www.w3.org/TR/webauthn-3/#example-bdbd14cc
+ // Note that this sample authenticator only supports ES256 keys.
+ const int8_t kty = 2; // Key type is EC2
+ const int8_t crv = 1; // Curve is P-256
+ const int8_t alg = -7; // Algorithm is ES256
+
+ bufferSize += CborLite::encodeInteger(buffer, ktyIndex);
+ bufferSize += CborLite::encodeInteger(buffer, kty);
+ bufferSize += CborLite::encodeInteger(buffer, algIndex);
+ bufferSize += CborLite::encodeInteger(buffer, alg);
+ bufferSize += CborLite::encodeInteger(buffer, crvIndex);
+ bufferSize += CborLite::encodeInteger(buffer, crv);
+ bufferSize += CborLite::encodeInteger(buffer, xIndex);
+ bufferSize += CborLite::encodeBytes(buffer, xCoord);
+ bufferSize += CborLite::encodeInteger(buffer, yIndex);
+ bufferSize += CborLite::encodeBytes(buffer, yCoord);
+
+ wil::unique_bcrypt_hash hashHandle;
+ THROW_IF_NTSTATUS_FAILED(BCryptCreateHash(
+ BCRYPT_SHA256_ALG_HANDLE,
+ &hashHandle,
+ nullptr,
+ 0,
+ nullptr,
+ 0,
+ 0));
+
+ THROW_IF_NTSTATUS_FAILED(BCryptHashData(hashHandle.get(), reinterpret_cast(pbXCoord), cbXCoord, 0));
+ THROW_IF_NTSTATUS_FAILED(BCryptHashData(hashHandle.get(), reinterpret_cast(pbYCoord), cbYCoord, 0));
+
+ DWORD cbHash = 0;
+ DWORD bytesRead = 0;
+ THROW_IF_NTSTATUS_FAILED(BCryptGetProperty(
+ hashHandle.get(),
+ BCRYPT_HASH_LENGTH,
+ reinterpret_cast(&cbHash),
+ sizeof(cbHash),
+ &bytesRead,
+ 0));
+
+ wil::unique_hlocal_ptr pbCredentialId = wil::make_unique_hlocal(cbHash);
+ THROW_IF_NTSTATUS_FAILED(BCryptFinishHash(hashHandle.get(), pbCredentialId.get(), cbHash, 0));
+
+ // Close the key and hash handle
+ hKey.reset();
+ hashHandle.reset();
+
+ com_ptr curApp = winrt::Microsoft::UI::Xaml::Application::Current().as();
+ PluginOperationType operationType = PLUGIN_OPERATION_TYPE_MAKE_CREDENTIAL;
+ if (curApp &&
+ curApp->m_pluginOperationOptions.operationType == PLUGIN_OPERATION_TYPE_GET_ASSERTION)
+ {
+ operationType = PLUGIN_OPERATION_TYPE_GET_ASSERTION;
+ }
+
+ // Refer to learn about packing credential data https://www.w3.org/TR/webauthn-3/#sctn-authenticator-data
+ const DWORD rpidsha256Size = 32; // SHA256 hash of rpId
+ const DWORD flagsSize = 1; // flags
+ const DWORD signCountSize = 4; // signCount
+ DWORD cbPackedAuthenticatorData = rpidsha256Size + flagsSize + signCountSize;
+
+ if (operationType == PLUGIN_OPERATION_TYPE_MAKE_CREDENTIAL)
+ {
+ cbPackedAuthenticatorData += sizeof(GUID); // aaGuid
+ cbPackedAuthenticatorData += sizeof(WORD); // credentialId length
+ cbPackedAuthenticatorData += cbHash; // credentialId
+ cbPackedAuthenticatorData += static_cast(buffer.size()); // public key
+ }
+
+ std::vector vPackedAuthenticatorData(cbPackedAuthenticatorData);
+ auto writer = buffer_writer{ vPackedAuthenticatorData };
+
+ auto rgbRpIdHash = writer.reserve_space>(); // 32 bytes of rpIdHash which is SHA256 hash of rpName. https://www.w3.org/TR/webauthn-3/#sctn-authenticator-data
+ DWORD cbRpIdHash;
+ THROW_IF_WIN32_BOOL_FALSE(CryptHashCertificate2(BCRYPT_SHA256_ALGORITHM,
+ 0,
+ nullptr,
+ pbRpId,
+ cbRpId,
+ rgbRpIdHash->data(),
+ &cbRpIdHash));
+
+ // Flags uv, up, be, and at are set
+ if (operationType == PLUGIN_OPERATION_TYPE_GET_ASSERTION)
+ {
+ // Refer https://www.w3.org/TR/webauthn-3/#authdata-flags
+ *writer.reserve_space() = 0x1d; // credential data flags of size 1 byte
+
+ *writer.reserve_space() = 0u; // Sign count of size 4 bytes is set to 0
+
+ vCredentialIdBuffer.assign(pbCredentialId.get(), pbCredentialId.get() + cbHash);
+ }
+ else
+ {
+ // Refer https://www.w3.org/TR/webauthn-3/#authdata-flags
+ *writer.reserve_space() = 0x5d; // credential data flags of size 1 byte
+
+ *writer.reserve_space() = 0u; // Sign count of size 4 bytes is set to 0
+
+ *writer.reserve_space() = GUID_NULL; // aaGuid of size 16 bytes is set to 0
+
+ // Retrieve credential id
+ WORD cbCredentialId = static_cast(cbHash);
+ WORD cbCredentialIdBigEndian = _byteswap_ushort(cbCredentialId);
+
+ *writer.reserve_space() = cbCredentialIdBigEndian; // Size of credential id in unsigned big endian of size 2 bytes
+
+ writer.add(std::span(pbCredentialId.get(), cbHash)); // Set credential id
+
+ vCredentialIdBuffer.assign(pbCredentialId.get(), pbCredentialId.get() + cbHash);
+
+ writer.add(std::span(buffer.data(), buffer.size())); // Set CBOR encoded public key
+ }
+
+ pcbPackedAuthenticatorData = static_cast(vPackedAuthenticatorData.size());
+ ppbpackedAuthenticatorData = wil::make_unique_hlocal(pcbPackedAuthenticatorData);
+ memcpy_s(ppbpackedAuthenticatorData.get(), pcbPackedAuthenticatorData, vPackedAuthenticatorData.data(), pcbPackedAuthenticatorData);
+
+ return S_OK;
+ }
+
+ /*
+ * This function is invoked by the platform to request the plugin to handle a make credential operation.
+ * Refer: pluginauthenticator.h/pluginauthenticator.idl
+ */
+ HRESULT STDMETHODCALLTYPE ContosoPlugin::EXPERIMENTAL_PluginMakeCredential(
+ /* [in] */ __RPC__in EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST pPluginMakeCredentialRequest,
+ /* [out] */ __RPC__deref_out_opt EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE* response) noexcept
+ {
+ try
+ {
+ SetEvent(App::s_pluginOpRequestRecievedEvent.get()); // indicate COM message received
+ DWORD hIndex = 0;
+ RETURN_IF_FAILED(CoWaitForMultipleHandles( // wait for app to be ready
+ COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS,
+ INFINITE,
+ 1,
+ App::s_hAppReadyForPluginOpEvent.addressof(),
+ &hIndex));
+ com_ptr curApp = winrt::Microsoft::UI::Xaml::Application::Current().as();
+
+ wil::shared_hmodule webauthnDll(LoadLibraryExW(L"webauthn.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32));
+ if (webauthnDll == nullptr)
+ {
+ return E_ABORT;
+ }
+
+ wil::unique_cotaskmem_ptr pDecodedMakeCredentialRequest;
+ auto webauthnDecodeMakeCredentialRequest = GetProcAddressByFunctionDeclaration(webauthnDll.get(), EXPERIMENTAL_WebAuthNDecodeMakeCredentialRequest);
+ THROW_IF_FAILED(webauthnDecodeMakeCredentialRequest(
+ pPluginMakeCredentialRequest->cbEncodedRequest,
+ pPluginMakeCredentialRequest->pbEncodedRequest,
+ wil::out_param(pDecodedMakeCredentialRequest)));
+ auto rpName = wil::make_cotaskmem_string(pDecodedMakeCredentialRequest->pRpInformation->pwszName);
+ auto userName = wil::make_cotaskmem_string(pDecodedMakeCredentialRequest->pUserInformation->pwszName);
+ std::vector requestBuffer(
+ pPluginMakeCredentialRequest->pbEncodedRequest,
+ pPluginMakeCredentialRequest->pbEncodedRequest + pPluginMakeCredentialRequest->cbEncodedRequest);
+
+ auto ppbPubKeyData = GetRequestSigningPubKey();
+ HRESULT requestSignResult = E_FAIL;
+ if (!ppbPubKeyData.empty())
+ {
+ requestSignResult = VerifySignatureHelper(
+ requestBuffer,
+ ppbPubKeyData.data(),
+ static_cast(ppbPubKeyData.size()),
+ pPluginMakeCredentialRequest->pbRequestSignature,
+ pPluginMakeCredentialRequest->cbRequestSignature);
+ }
+ {
+ std::lock_guard lock(curApp->m_pluginOperationOptionsMutex);
+ curApp->m_pluginOperationStatus.requestSignatureVerificationStatus = requestSignResult;
+ }
+
+ THROW_IF_FAILED(PerformUv(curApp,
+ pPluginMakeCredentialRequest->hWnd,
+ webauthnDll,
+ pPluginMakeCredentialRequest->transactionId,
+ PLUGIN_OPERATION_TYPE_MAKE_CREDENTIAL,
+ requestBuffer,
+ std::move(rpName),
+ std::move(userName)));
+
+ //create a persisted key using ncrypt
+ wil::unique_ncrypt_prov hProvider;
+ wil::unique_ncrypt_key hKey;
+
+ // get the provider
+ THROW_IF_FAILED(NCryptOpenStorageProvider(&hProvider, nullptr, 0));
+
+ // get the user handle as a string
+ std::wstring keyNameStr = contosoplugin_key_domain;
+ std::wstringstream keyNameStream;
+ for (DWORD idx = 0; idx < pDecodedMakeCredentialRequest->pUserInformation->cbId; idx++)
+ {
+ keyNameStream << std::hex << std::setw(2) << std::setfill(L'0') <<
+ static_cast(pDecodedMakeCredentialRequest->pUserInformation->pbId[idx]);
+ }
+ keyNameStr += keyNameStream.str();
+
+ // create the key
+ THROW_IF_FAILED(NCryptCreatePersistedKey(
+ hProvider.get(),
+ &hKey,
+ BCRYPT_ECDH_P256_ALGORITHM,
+ keyNameStr.c_str(),
+ 0,
+ NCRYPT_OVERWRITE_KEY_FLAG));
+
+ // set the export policy
+ DWORD exportPolicy = NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
+ THROW_IF_FAILED(NCryptSetProperty(
+ hKey.get(),
+ NCRYPT_EXPORT_POLICY_PROPERTY,
+ reinterpret_cast(&exportPolicy),
+ sizeof(exportPolicy),
+ NCRYPT_PERSIST_FLAG));
+
+ // allow both signing and encryption
+ DWORD keyUsage = NCRYPT_ALLOW_SIGNING_FLAG | NCRYPT_ALLOW_DECRYPT_FLAG;
+ THROW_IF_FAILED(NCryptSetProperty(
+ hKey.get(),
+ NCRYPT_KEY_USAGE_PROPERTY,
+ reinterpret_cast(&keyUsage),
+ sizeof(keyUsage),
+ NCRYPT_PERSIST_FLAG));
+ HWND hWnd;
+ if (curApp->m_silentMode)
+ {
+ hWnd = curApp->m_pluginOperationOptions.hWnd;
+ }
+ else
+ {
+ hWnd = curApp->GetNativeWindowHandle();
+ }
+ THROW_IF_FAILED(NCryptSetProperty(
+ hKey.get(),
+ NCRYPT_WINDOW_HANDLE_PROPERTY,
+ reinterpret_cast(&hWnd),
+ sizeof(HWND),
+ 0));
+
+ // finalize the key
+ THROW_IF_FAILED(NCryptFinalizeKey(hKey.get(), 0));
+
+ DWORD cbPackedAuthenticatorData = 0;
+ wil::unique_hlocal_ptr packedAuthenticatorData;
+ std::vector vCredentialIdBuffer;
+ THROW_IF_FAILED(CreateAuthenticatorData(
+ std::move(hKey),
+ pDecodedMakeCredentialRequest->cbRpId,
+ pDecodedMakeCredentialRequest->pbRpId,
+ cbPackedAuthenticatorData,
+ packedAuthenticatorData,
+ vCredentialIdBuffer));
+
+ auto operationResponse = wil::make_unique_cotaskmem();
+
+ WEBAUTHN_CREDENTIAL_ATTESTATION attestationResponse{};
+ attestationResponse.dwVersion = WEBAUTHN_CREDENTIAL_ATTESTATION_CURRENT_VERSION;
+ attestationResponse.pwszFormatType = WEBAUTHN_ATTESTATION_TYPE_NONE;
+ attestationResponse.cbAttestation = 0;
+ attestationResponse.pbAttestation = nullptr;
+ attestationResponse.cbAuthenticatorData = 0;
+ attestationResponse.pbAuthenticatorData = nullptr;
+
+ attestationResponse.pbAuthenticatorData = packedAuthenticatorData.get();
+ attestationResponse.cbAuthenticatorData = cbPackedAuthenticatorData;
+
+ DWORD cbAttestationBuffer = 0;
+ PBYTE pbattestationBuffer;
+
+ auto webauthnEncodeMakeCredentialResponse = GetProcAddressByFunctionDeclaration(webauthnDll.get(), EXPERIMENTAL_WebAuthNEncodeMakeCredentialResponse);
+ THROW_IF_FAILED(webauthnEncodeMakeCredentialResponse(
+ &attestationResponse,
+ &cbAttestationBuffer,
+ &pbattestationBuffer));
+ operationResponse->cbEncodedResponse = cbAttestationBuffer;
+ operationResponse->pbEncodedResponse = wil::make_unique_cotaskmem(cbAttestationBuffer).release();
+ memcpy_s(operationResponse->pbEncodedResponse,
+ operationResponse->cbEncodedResponse,
+ pbattestationBuffer,
+ cbAttestationBuffer);
+
+ *response = operationResponse.release();
+
+ WEBAUTHN_CREDENTIAL_DETAILS credentialDetails{};
+ credentialDetails.dwVersion = WEBAUTHN_CREDENTIAL_DETAILS_CURRENT_VERSION;
+ credentialDetails.pUserInformation = const_cast(pDecodedMakeCredentialRequest->pUserInformation);
+ credentialDetails.pRpInformation = const_cast(pDecodedMakeCredentialRequest->pRpInformation);
+ credentialDetails.cbCredentialID = static_cast(vCredentialIdBuffer.size());
+ credentialDetails.pbCredentialID = wil::make_unique_cotaskmem(vCredentialIdBuffer.size()).release();
+ memcpy_s(credentialDetails.pbCredentialID, credentialDetails.cbCredentialID, vCredentialIdBuffer.data(), static_cast(vCredentialIdBuffer.size()));
+ if (!PluginCredentialManager::getInstance().SaveCredentialMetadataToMockDB(credentialDetails))
+ {
+ std::lock_guard lock(curApp->m_pluginOperationOptionsMutex);
+ curApp->m_pluginOperationStatus.performOperationStatus = E_FAIL;
+ }
+ pDecodedMakeCredentialRequest.reset();
+ SetEvent(App::s_hPluginOpCompletedEvent.get());
+ return S_OK;
+ }
+ catch (...)
+ {
+ HRESULT hr = wil::ResultFromCaughtException();
+ com_ptr curApp = winrt::Microsoft::UI::Xaml::Application::Current().as();
+ if (curApp)
+ {
+ hr = winrt::to_hresult();
+ std::lock_guard lock(curApp->m_pluginOperationOptionsMutex);
+ curApp->m_pluginOperationStatus.performOperationStatus = hr;
+ };
+ SetEvent(App::s_hPluginOpCompletedEvent.get());
+ return hr;
+ }
+ }
+
+ /*
+ * This function is invoked by the platform to request the plugin to handle a get assertion operation.
+ * Refer: pluginauthenticator.h/pluginauthenticator.idl
+ */
+ HRESULT STDMETHODCALLTYPE ContosoPlugin::EXPERIMENTAL_PluginGetAssertion(
+ /* [in] */ __RPC__in EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST pPluginGetAssertionRequest,
+ /* [out] */ __RPC__deref_out_opt EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE* response) noexcept
+ {
+ try
+ {
+ SetEvent(App::s_pluginOpRequestRecievedEvent.get());
+ DWORD hIndex = 0;
+ RETURN_IF_FAILED(CoWaitForMultipleHandles(
+ COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS,
+ INFINITE,
+ 1,
+ App::s_hAppReadyForPluginOpEvent.addressof(),
+ &hIndex));
+ com_ptr curApp = winrt::Microsoft::UI::Xaml::Application::Current().as();
+
+ wil::shared_hmodule webauthnDll(LoadLibraryExW(L"webauthn.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32));
+ if (webauthnDll == nullptr)
+ {
+ return E_ABORT;
+ }
+
+ wil::unique_cotaskmem_ptr pDecodedAssertionRequest;
+ // The EXPERIMENTAL_WebAuthNDecodeGetAssertionRequest function can be optionally used to decode the CBOR encoded request to a EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST structure.
+ auto webauthnDecodeGetAssertionRequest = GetProcAddressByFunctionDeclaration(webauthnDll.get(), EXPERIMENTAL_WebAuthNDecodeGetAssertionRequest);
+ webauthnDecodeGetAssertionRequest(pPluginGetAssertionRequest->cbEncodedRequest, pPluginGetAssertionRequest->pbEncodedRequest, wil::out_param(pDecodedAssertionRequest));
+ wil::shared_cotaskmem_string rpName = wil::make_cotaskmem_string(pDecodedAssertionRequest->pwszRpId);
+ //load the user handle
+ auto& credManager = PluginCredentialManager::getInstance();
+ const WEBAUTHN_CREDENTIAL_DETAILS* selectedCredential{};
+ // create a list of credentials
+ std::vector selectedCredentials;
+
+ while (true)
+ {
+ Sleep(100);
+ if (credManager.IsLocalCredentialMetadataLoaded())
+ {
+ credManager.GetLocalCredsByRpIdAndAllowList(pDecodedAssertionRequest->pwszRpId,
+ pDecodedAssertionRequest->CredentialList.ppCredentials,
+ pDecodedAssertionRequest->CredentialList.cCredentials,
+ selectedCredentials);
+ break;
+ }
+ }
+
+ if (selectedCredentials.empty())
+ {
+ {
+ std::lock_guard lock(curApp->m_pluginOperationOptionsMutex);
+ curApp->m_pluginOperationStatus.performOperationStatus = NTE_NOT_FOUND;
+ }
+ SetEvent(App::s_hPluginOpCompletedEvent.get());
+ return NTE_NOT_FOUND;
+ }
+ else if (selectedCredentials.size() == 1 && credManager.GetSilentOperation())
+ {
+ selectedCredential = selectedCredentials[0];
+ }
+ else
+ {
+ curApp->SetMatchingCredentials(pDecodedAssertionRequest->pwszRpId, selectedCredentials, pPluginGetAssertionRequest->hWnd);
+ hIndex = 0;
+ RETURN_IF_FAILED(CoWaitForMultipleHandles(COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS, INFINITE, 1, curApp->m_hPluginCredentialSelected.addressof(), &hIndex));
+
+ {
+ std::lock_guard lock(curApp->m_pluginOperationOptionsMutex);
+ selectedCredential = curApp->m_pluginOperationOptions.selectedCredential;
+ }
+
+ // Failed to select a credential
+ if (selectedCredential->cbCredentialID == 0 ||
+ selectedCredential->pbCredentialID == nullptr ||
+ selectedCredential->pUserInformation == nullptr ||
+ selectedCredential->pUserInformation->pwszName == nullptr)
+ {
+ {
+ std::lock_guard lock(curApp->m_pluginOperationOptionsMutex);
+ curApp->m_pluginOperationStatus.performOperationStatus = NTE_NOT_FOUND;
+ }
+ SetEvent(App::s_hPluginOpCompletedEvent.get());
+ return NTE_NOT_FOUND;
+ }
+ }
+
+ wil::shared_cotaskmem_string userName = wil::make_cotaskmem_string(selectedCredential->pUserInformation->pwszName);
+
+ std::vector requestBuffer(
+ pPluginGetAssertionRequest->pbEncodedRequest,
+ pPluginGetAssertionRequest->pbEncodedRequest + pPluginGetAssertionRequest->cbEncodedRequest);
+
+ auto ppbPubKeyData = GetRequestSigningPubKey();
+ HRESULT requestSignResult = E_FAIL;
+ if (!ppbPubKeyData.empty())
+ {
+ requestSignResult = VerifySignatureHelper(
+ requestBuffer,
+ ppbPubKeyData.data(),
+ static_cast(ppbPubKeyData.size()),
+ pPluginGetAssertionRequest->pbRequestSignature,
+ pPluginGetAssertionRequest->cbRequestSignature);
+ }
+
+ {
+ std::lock_guard lock(curApp->m_pluginOperationOptionsMutex);
+ curApp->m_pluginOperationStatus.requestSignatureVerificationStatus = requestSignResult;
+ }
+
+ THROW_IF_FAILED(PerformUv(curApp,
+ pPluginGetAssertionRequest->hWnd,
+ webauthnDll,
+ pPluginGetAssertionRequest->transactionId,
+ PLUGIN_OPERATION_TYPE_GET_ASSERTION,
+ requestBuffer,
+ rpName,
+ userName));
+
+ // convert user handle to a string
+ std::wstring keyNameStr = contosoplugin_key_domain;
+ std::wstringstream keyNameStream;
+ for (DWORD idx = 0; idx < selectedCredential->pUserInformation->cbId; idx++)
+ {
+ keyNameStream << std::hex << std::setw(2) << std::setfill(L'0') <<
+ static_cast(selectedCredential->pUserInformation->pbId[idx]);
+ }
+ keyNameStr += keyNameStream.str();
+
+ //open the key using ncrypt and sign the data
+ wil::unique_ncrypt_prov hProvider;
+ wil::shared_ncrypt_key hKey;
+
+ // get the provider
+ THROW_IF_FAILED(NCryptOpenStorageProvider(&hProvider, nullptr, 0));
+
+ // open the key
+ THROW_IF_FAILED(NCryptOpenKey(hProvider.get(), &hKey, keyNameStr.c_str(), 0, 0));
+
+ // set hwnd property
+ wil::unique_hwnd hWnd;
+ if (curApp->m_silentMode)
+ {
+ hWnd.reset(curApp->m_pluginOperationOptions.hWnd);
+ }
+ else
+ {
+ hWnd.reset(curApp->GetNativeWindowHandle());
+ }
+ THROW_IF_FAILED(NCryptSetProperty(
+ hKey.get(),
+ NCRYPT_WINDOW_HANDLE_PROPERTY,
+ (BYTE*)(hWnd.addressof()),
+ sizeof(HWND),
+ 0));
+
+ // create authenticator data
+ DWORD cbPackedAuthenticatorData = 0;
+ wil::unique_hlocal_ptr packedAuthenticatorData;
+ std::vector vCredentialIdBuffer;
+ THROW_IF_FAILED(CreateAuthenticatorData(hKey,
+ pDecodedAssertionRequest->cbRpId,
+ pDecodedAssertionRequest->pbRpId,
+ cbPackedAuthenticatorData,
+ packedAuthenticatorData,
+ vCredentialIdBuffer));
+
+ wil::unique_hlocal_ptr pbSignature = nullptr;
+ DWORD cbSignature = 0;
+
+ {
+ wil::unique_bcrypt_hash hashHandle;
+
+
+ THROW_IF_NTSTATUS_FAILED(BCryptCreateHash(
+ BCRYPT_SHA256_ALG_HANDLE,
+ &hashHandle,
+ nullptr,
+ 0,
+ nullptr,
+ 0,
+ 0));
+
+ THROW_IF_NTSTATUS_FAILED(BCryptHashData(hashHandle.get(), const_cast(packedAuthenticatorData.get()), cbPackedAuthenticatorData, 0));
+ THROW_IF_NTSTATUS_FAILED(BCryptHashData(hashHandle.get(), const_cast(pDecodedAssertionRequest->pbClientDataHash), pDecodedAssertionRequest->cbClientDataHash, 0));
+
+ DWORD bytesRead = 0;
+ DWORD cbSignatureBuffer = 0;
+ THROW_IF_NTSTATUS_FAILED(BCryptGetProperty(
+ hashHandle.get(),
+ BCRYPT_HASH_LENGTH,
+ reinterpret_cast(&cbSignatureBuffer),
+ sizeof(cbSignatureBuffer),
+ &bytesRead,
+ 0));
+
+ wil::unique_hlocal_ptr signatureBuffer = wil::make_unique_hlocal(cbSignatureBuffer);
+ THROW_HR_IF(E_UNEXPECTED, signatureBuffer == nullptr);
+ THROW_IF_NTSTATUS_FAILED(BCryptFinishHash(hashHandle.get(), signatureBuffer.get(), cbSignatureBuffer, 0));
+
+ // sign the data
+ THROW_IF_FAILED(NCryptSignHash(hKey.get(), nullptr, signatureBuffer.get(), cbSignatureBuffer, nullptr, 0, &cbSignature, 0));
+
+ pbSignature = wil::make_unique_hlocal(cbSignature);
+ THROW_HR_IF(E_UNEXPECTED, pbSignature == nullptr);
+
+ THROW_IF_FAILED(NCryptSignHash(hKey.get(), nullptr, signatureBuffer.get(), cbSignatureBuffer, pbSignature.get(), cbSignature, &cbSignature, 0));
+ signatureBuffer.reset();
+
+ auto encodeSignature = [](PBYTE signature, size_t signatureSize)
+ {
+ std::vector encodedSignature{};
+ encodedSignature.push_back(0x02); // ASN integer tag
+ encodedSignature.push_back(static_cast(signatureSize)); // length of the signature
+ if (WI_IsFlagSet(signature[0], 0x80))
+ {
+ encodedSignature[encodedSignature.size() - 1]++;
+ encodedSignature.push_back(0x00); // add a padding byte if the first byte has the high bit set
+ }
+
+ encodedSignature.insert(encodedSignature.end(), signature, signature + signatureSize);
+ return encodedSignature;
+ };
+
+ auto signatureR = encodeSignature(pbSignature.get(), cbSignature / 2);
+ auto signatureS = encodeSignature(pbSignature.get() + cbSignature / 2, cbSignature / 2);
+
+ std::vector encodedSignature{};
+ encodedSignature.push_back(0x30); // ASN sequence tag
+ encodedSignature.push_back(static_cast(signatureR.size() + signatureS.size())); // length of the sequence
+ encodedSignature.insert(encodedSignature.end(), signatureR.begin(), signatureR.end());
+ encodedSignature.insert(encodedSignature.end(), signatureS.begin(), signatureS.end());
+
+ cbSignature = static_cast(encodedSignature.size());
+ pbSignature.reset();
+ pbSignature = wil::make_unique_hlocal(cbSignature);
+ THROW_HR_IF(E_UNEXPECTED, pbSignature == nullptr);
+ memcpy_s(pbSignature.get(), cbSignature, encodedSignature.data(), static_cast(cbSignature));
+ }
+
+ // create the response
+ auto operationResponse = wil::make_unique_cotaskmem();
+
+ auto assertionResponse = wil::make_unique_cotaskmem();
+ assertionResponse->dwVersion = WEBAUTHN_ASSERTION_CURRENT_VERSION;
+
+ // [1] Credential (optional)
+ assertionResponse->Credential.dwVersion = WEBAUTHN_CREDENTIAL_CURRENT_VERSION;
+ assertionResponse->Credential.cbId = static_cast(vCredentialIdBuffer.size());
+ assertionResponse->Credential.pbId = vCredentialIdBuffer.data();
+ assertionResponse->Credential.pwszCredentialType = WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY;
+
+ // [2] AuthenticatorData
+ assertionResponse->cbAuthenticatorData = cbPackedAuthenticatorData;
+ assertionResponse->pbAuthenticatorData = packedAuthenticatorData.get();
+
+ // [3] Signature
+ assertionResponse->cbSignature = cbSignature;
+ assertionResponse->pbSignature = pbSignature.get();
+
+ // [4] User (optional)
+ assertionResponse->cbUserId = selectedCredential->pUserInformation->cbId;
+ auto userIdBuffer = wil::make_unique_cotaskmem(selectedCredential->pUserInformation->cbId);
+ memcpy_s(userIdBuffer.get(),
+ selectedCredential->pUserInformation->cbId,
+ selectedCredential->pUserInformation->pbId,
+ selectedCredential->pUserInformation->cbId);
+ assertionResponse->pbUserId = userIdBuffer.get();
+ WEBAUTHN_USER_ENTITY_INFORMATION userEntityInformation{};
+ userEntityInformation.dwVersion = WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION;
+ userEntityInformation.cbId = assertionResponse->cbUserId;
+ userEntityInformation.pbId = assertionResponse->pbUserId;
+
+ auto ctapGetAssertionResponse = wil::make_unique_cotaskmem();
+ ctapGetAssertionResponse->WebAuthNAssertion = *(assertionResponse.get()); // [1] Credential, [2] AuthenticatorData, [3] Signature
+ ctapGetAssertionResponse->pUserInformation = &userEntityInformation; // [4] User
+ ctapGetAssertionResponse->dwNumberOfCredentials = 1; // [5] NumberOfCredentials
+
+ DWORD cbAssertionBuffer = 0;
+ PBYTE pbAssertionBuffer;
+
+ // The EXPERIMENTAL_WebAuthNEncodeGetAssertionResponse function can be optionally used to encode the
+ // EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_RESPONSE structure to a CBOR encoded response.
+ auto webAuthNEncodeGetAssertionResponse = GetProcAddressByFunctionDeclaration(webauthnDll.get(), EXPERIMENTAL_WebAuthNEncodeGetAssertionResponse);
+ THROW_IF_FAILED(webAuthNEncodeGetAssertionResponse(
+ (EXPERIMENTAL_PCWEBAUTHN_CTAPCBOR_GET_ASSERTION_RESPONSE)(ctapGetAssertionResponse.get()),
+ &cbAssertionBuffer,
+ &pbAssertionBuffer));
+
+ assertionResponse.reset();
+ ctapGetAssertionResponse.reset();
+ userIdBuffer.reset();
+ packedAuthenticatorData.reset();
+ pbSignature.reset();
+ pDecodedAssertionRequest.reset();
+
+ operationResponse->cbEncodedResponse = cbAssertionBuffer;
+ // pbEncodedResponse must contain a CBOR encoded response as specified the FIDO CTAP.
+ // Refer: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#message-encoding.
+ operationResponse->pbEncodedResponse = wil::make_unique_cotaskmem(cbAssertionBuffer).release();
+ memcpy_s(
+ operationResponse->pbEncodedResponse,
+ operationResponse->cbEncodedResponse,
+ pbAssertionBuffer,
+ cbAssertionBuffer);
+
+ *response = operationResponse.release();
+ SetEvent(App::s_hPluginOpCompletedEvent.get());
+ return S_OK;
+ }
+ catch (...)
+ {
+ HRESULT localHr = wil::ResultFromCaughtException();
+ {
+ winrt::com_ptr curApp = winrt::Microsoft::UI::Xaml::Application::Current().as();
+ std::lock_guard lock(curApp->m_pluginOperationOptionsMutex);
+ curApp->m_pluginOperationStatus.performOperationStatus = localHr;
+ }
+ SetEvent(App::s_hPluginOpCompletedEvent.get());
+ return localHr;
+ }
+ }
+
+ /*
+ * This function is invoked by the platform to request the plugin to cancel an ongoing operation.
+ */
+ HRESULT STDMETHODCALLTYPE ContosoPlugin::EXPERIMENTAL_PluginCancelOperation(
+ /* [out] */ __RPC__in EXPERIMENTAL_PCWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST)
+ {
+ SetEvent(App::s_pluginOpRequestRecievedEvent.get());
+ com_ptr curApp = winrt::Microsoft::UI::Xaml::Application::Current().as();
+ curApp->GetDispatcherQueue().TryEnqueue([curApp]()
+ {
+ curApp->PluginCancelAction();
+ });
+ return S_OK;
+ }
+
+ /*
+ * This is a sample implementation of a factory method that creates an instance of the Class that implements the EXPERIMENTAL_IPluginAuthenticator interface.
+ * Refer: pluginauthenticator.h/pluginauthenticator.idl for the interface definition.
+ */
+ HRESULT __stdcall ContosoPluginFactory::CreateInstance(
+ ::IUnknown* outer,
+ GUID const& iid,
+ void** result) noexcept
+ {
+ *result = nullptr;
+
+ if (outer)
+ {
+ return CLASS_E_NOAGGREGATION;
+ }
+
+ try
+ {
+ return make()->QueryInterface(iid, result);
+ }
+ catch (...)
+ {
+ return winrt::to_hresult();
+ }
+ }
+
+ HRESULT __stdcall ContosoPluginFactory::LockServer(BOOL) noexcept
+ {
+ return S_OK;
+ }
+
+}
diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs
new file mode 100644
index 00000000000..a306d42f60b
--- /dev/null
+++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs
@@ -0,0 +1,249 @@
+use std::alloc::{alloc, Layout};
+use std::ptr;
+use serde_json;
+use windows_core::{HRESULT, s};
+
+use crate::types::*;
+use crate::utils::{self as util, delay_load};
+use crate::com_provider::ExperimentalWebAuthnPluginOperationResponse;
+
+// Windows API types for WebAuthn (from webauthn.h.sample)
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST {
+ pub dwVersion: u32,
+ pub pwszRpId: *const u16, // PCWSTR
+ pub cbRpId: u32,
+ pub pbRpId: *const u8,
+ pub cbClientDataHash: u32,
+ pub pbClientDataHash: *const u8,
+ pub CredentialList: WEBAUTHN_CREDENTIAL_LIST,
+ pub cbCborExtensionsMap: u32,
+ pub pbCborExtensionsMap: *const u8,
+ // Add other fields as needed...
+}
+
+pub type PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST = *mut EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST;
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct WEBAUTHN_CREDENTIAL_LIST {
+ pub cCredentials: u32,
+ pub pCredentials: *const u8, // Placeholder
+}
+
+// Windows API function signatures for decoding get assertion requests
+type EXPERIMENTAL_WebAuthNDecodeGetAssertionRequestFn = unsafe extern "stdcall" fn(
+ cbEncoded: u32,
+ pbEncoded: *const u8,
+ ppGetAssertionRequest: *mut PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST,
+) -> HRESULT;
+
+type EXPERIMENTAL_WebAuthNFreeDecodedGetAssertionRequestFn = unsafe extern "stdcall" fn(
+ pGetAssertionRequest: PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST,
+);
+
+// RAII wrapper for decoded get assertion request
+pub struct DecodedGetAssertionRequest {
+ ptr: PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST,
+ free_fn: Option,
+}
+
+impl DecodedGetAssertionRequest {
+ fn new(ptr: PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST, free_fn: Option) -> Self {
+ Self { ptr, free_fn }
+ }
+
+ pub fn as_ref(&self) -> &EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST {
+ unsafe { &*self.ptr }
+ }
+}
+
+impl Drop for DecodedGetAssertionRequest {
+ fn drop(&mut self) {
+ if !self.ptr.is_null() {
+ if let Some(free_fn) = self.free_fn {
+ util::message("Freeing decoded get assertion request");
+ unsafe { free_fn(self.ptr); }
+ }
+ }
+ }
+}
+
+// Function to decode get assertion request using Windows API
+pub unsafe fn decode_get_assertion_request(encoded_request: &[u8]) -> Result {
+ util::message("Attempting to decode get assertion request using Windows API");
+
+ // Load the Windows WebAuthn API function
+ let decode_fn: Option = delay_load(
+ s!("webauthn.dll"),
+ s!("EXPERIMENTAL_WebAuthNDecodeGetAssertionRequest")
+ );
+
+ let decode_fn = decode_fn.ok_or("Failed to load EXPERIMENTAL_WebAuthNDecodeGetAssertionRequest from webauthn.dll")?;
+
+ // Load the free function
+ let free_fn: Option = delay_load(
+ s!("webauthn.dll"),
+ s!("EXPERIMENTAL_WebAuthNFreeDecodedGetAssertionRequest")
+ );
+
+ let mut pp_get_assertion_request: PEXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST = ptr::null_mut();
+
+ let result = decode_fn(
+ encoded_request.len() as u32,
+ encoded_request.as_ptr(),
+ &mut pp_get_assertion_request,
+ );
+
+ if result.is_err() || pp_get_assertion_request.is_null() {
+ return Err(format!("EXPERIMENTAL_WebAuthNDecodeGetAssertionRequest failed with HRESULT: {}", result.0));
+ }
+
+
+ Ok(DecodedGetAssertionRequest::new(pp_get_assertion_request, free_fn))
+}
+
+/// Context information parsed from the incoming request
+#[derive(Debug, Clone)]
+pub struct RequestContext {
+ pub rpid: Option,
+ pub allowed_credentials: Vec>,
+ pub user_verification: Option,
+ pub user_id: Option>,
+ pub user_name: Option,
+ pub user_display_name: Option,
+ pub client_data_hash: Option>,
+ pub supported_algorithms: Vec, // COSE algorithm identifiers
+}
+
+impl Default for RequestContext {
+ fn default() -> Self {
+ Self {
+ rpid: None,
+ allowed_credentials: Vec::new(),
+ user_verification: None,
+ user_id: None,
+ user_name: None,
+ user_display_name: None,
+ client_data_hash: None,
+ supported_algorithms: Vec::new(),
+ }
+ }
+}
+
+/// Helper for assertion requests
+pub fn send_assertion_request(rpid: &str, transaction_id: &str, context: &RequestContext) -> Option {
+ // Extract client data hash from context - this is required for WebAuthn
+ let client_data_hash = match &context.client_data_hash {
+ Some(hash) if !hash.is_empty() => hash.clone(),
+ _ => {
+ util::message("ERROR: Client data hash is required for assertion but not provided");
+ return None;
+ }
+ };
+
+ let request = PasskeyAssertionRequest {
+ rp_id: rpid.to_string(),
+ transaction_id: transaction_id.to_string(),
+ client_data_hash,
+ allowed_credentials: context.allowed_credentials.clone(),
+ user_verification: context.user_verification.unwrap_or(false),
+ };
+
+ util::message(&format!("Assertion request data - RP ID: {}, Client data hash: {} bytes, Allowed credentials: {}",
+ rpid, request.client_data_hash.len(), request.allowed_credentials.len()));
+
+ match serde_json::to_string(&request) {
+ Ok(request_json) => {
+ util::message(&format!("Sending assertion request: {}", request_json));
+ crate::ipc::send_passkey_request(RequestType::Assertion, request_json, rpid)
+ },
+ Err(e) => {
+ util::message(&format!("ERROR: Failed to serialize assertion request: {}", e));
+ None
+ }
+ }
+}
+
+/// Creates a WebAuthn get assertion response from Bitwarden's assertion response
+pub unsafe fn create_get_assertion_response(
+ credential_id: Vec,
+ authenticator_data: Vec,
+ signature: Vec,
+ user_handle: Vec
+) -> std::result::Result<*mut ExperimentalWebAuthnPluginOperationResponse, HRESULT> {
+ // Construct a CTAP2 response with the proper structure
+
+ // Create CTAP2 GetAssertion response map according to CTAP2 specification
+ let mut cbor_response: Vec<(ciborium::Value, ciborium::Value)> = Vec::new();
+
+ // [1] credential (optional) - Always include credential descriptor
+ let credential_map = vec![
+ (ciborium::Value::Text("id".to_string()), ciborium::Value::Bytes(credential_id.clone())),
+ (ciborium::Value::Text("type".to_string()), ciborium::Value::Text("public-key".to_string())),
+ ];
+ cbor_response.push((
+ ciborium::Value::Integer(1.into()),
+ ciborium::Value::Map(credential_map)
+ ));
+
+ // [2] authenticatorData (required)
+ cbor_response.push((
+ ciborium::Value::Integer(2.into()),
+ ciborium::Value::Bytes(authenticator_data)
+ ));
+
+ // [3] signature (required)
+ cbor_response.push((
+ ciborium::Value::Integer(3.into()),
+ ciborium::Value::Bytes(signature)
+ ));
+
+ // [4] user (optional) - include if user handle is provided
+ if !user_handle.is_empty() {
+ let user_map = vec![
+ (ciborium::Value::Text("id".to_string()), ciborium::Value::Bytes(user_handle)),
+ ];
+ cbor_response.push((
+ ciborium::Value::Integer(4.into()),
+ ciborium::Value::Map(user_map)
+ ));
+ }
+
+ let cbor_value = ciborium::Value::Map(cbor_response);
+
+ // Encode to CBOR with error handling
+ let mut cbor_data = Vec::new();
+ if let Err(e) = ciborium::ser::into_writer(&cbor_value, &mut cbor_data) {
+ util::message(&format!("ERROR: Failed to encode CBOR assertion response: {:?}", e));
+ return Err(HRESULT(-1));
+ }
+
+ let response_len = cbor_data.len();
+
+ // Allocate memory for the response data
+ let layout = Layout::from_size_align(response_len, 1).map_err(|_| HRESULT(-1))?;
+ let response_ptr = alloc(layout);
+ if response_ptr.is_null() {
+ return Err(HRESULT(-1));
+ }
+
+ // Copy response data
+ ptr::copy_nonoverlapping(cbor_data.as_ptr(), response_ptr, response_len);
+
+ // Allocate memory for the response structure
+ let response_layout = Layout::new::();
+ let operation_response_ptr = alloc(response_layout) as *mut ExperimentalWebAuthnPluginOperationResponse;
+ if operation_response_ptr.is_null() {
+ return Err(HRESULT(-1));
+ }
+
+ // Initialize the response
+ ptr::write(operation_response_ptr, ExperimentalWebAuthnPluginOperationResponse {
+ encoded_response_byte_count: response_len as u32,
+ encoded_response_pointer: response_ptr,
+ });
+
+ Ok(operation_response_ptr)
+}
\ No newline at end of file
diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_buffer.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_buffer.rs
new file mode 100644
index 00000000000..af34107a050
--- /dev/null
+++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_buffer.rs
@@ -0,0 +1,84 @@
+use std::alloc;
+use std::mem::{align_of, MaybeUninit};
+use std::ptr::NonNull;
+use windows::Win32::System::Com::CoTaskMemAlloc;
+
+#[repr(transparent)]
+pub struct ComBuffer(NonNull>);
+
+impl ComBuffer {
+ /// Returns an COM-allocated buffer of `size`.
+ fn alloc(size: usize, for_slice: bool) -> Self {
+ #[expect(clippy::as_conversions)]
+ {
+ assert!(size <= isize::MAX as usize, "requested bad object size");
+ }
+
+ // SAFETY: Any size is valid to pass to Windows, even `0`.
+ let ptr = NonNull::new(unsafe { CoTaskMemAlloc(size) }).unwrap_or_else(|| {
+ // XXX: This doesn't have to be correct, just close enough for an OK OOM error.
+ let layout = alloc::Layout::from_size_align(size, align_of::()).unwrap();
+ alloc::handle_alloc_error(layout)
+ });
+
+ if for_slice {
+ // Ininitialize the buffer so it can later be treated as `&mut [u8]`.
+ // SAFETY: The pointer is valid and we are using a valid value for a byte-wise allocation.
+ unsafe { ptr.write_bytes(0, size) };
+ }
+
+ Self(ptr.cast())
+ }
+
+ fn into_ptr(self) -> *mut T {
+ self.0.cast().as_ptr()
+ }
+
+ /// Creates a new COM-allocated structure.
+ ///
+ /// Note that `T` must be [Copy] to avoid any possible memory leaks.
+ pub fn with_object(object: T) -> *mut T {
+ // NB: Vendored from Rust's alloc code since we can't yet allocate `Box` with a custom allocator.
+ const MIN_ALIGN: usize = if cfg!(target_pointer_width = "64") {
+ 16
+ } else if cfg!(target_pointer_width = "32") {
+ 8
+ } else {
+ panic!("unsupported arch")
+ };
+
+ // SAFETY: Validate that our alignment works for a normal size-based allocation for soundness.
+ let layout = const {
+ let layout = alloc::Layout::new::();
+ assert!(layout.align() <= MIN_ALIGN);
+ layout
+ };
+
+ let buffer = Self::alloc(layout.size(), false);
+ // SAFETY: `ptr` is valid for writes of `T` because we correctly allocated the right sized buffer that
+ // accounts for any alignment requirements.
+ //
+ // Additionally, we ensure the value is treated as moved by forgetting the source.
+ unsafe { buffer.0.cast::().write(object) };
+ buffer.into_ptr()
+ }
+
+ pub fn from_buffer>(buffer: T) -> (*mut u8, u32) {
+ let buffer = buffer.as_ref();
+ let len = buffer.len();
+ let com_buffer = Self::alloc(len, true);
+
+ // SAFETY: `ptr` points to a valid allocation that `len` matches, and we made sure
+ // the bytes were initialized. Additionally, bytes have no alignment requirements.
+ unsafe {
+ NonNull::slice_from_raw_parts(com_buffer.0.cast::(), len)
+ .as_mut()
+ .copy_from_slice(buffer)
+ }
+
+ // Safety: The Windows API structures these buffers are placed into use `u32` (`DWORD`) to
+ // represent length.
+ #[expect(clippy::as_conversions)]
+ (com_buffer.into_ptr(), len as u32)
+ }
+}
diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_provider.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_provider.rs
new file mode 100644
index 00000000000..55a9b23e240
--- /dev/null
+++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_provider.rs
@@ -0,0 +1,417 @@
+use windows::Win32::System::Com::*;
+use windows_core::{implement, interface, IInspectable, IUnknown, Interface, HRESULT};
+use std::ptr;
+
+use crate::types::*;
+use crate::utils::{self as util, wstr_to_string};
+use crate::assert::{RequestContext, decode_get_assertion_request, create_get_assertion_response, send_assertion_request};
+use crate::make_credential::{decode_make_credential_request, create_make_credential_response, send_registration_request};
+
+/// Used when creating and asserting credentials.
+/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_REQUEST
+/// Header File Usage: EXPERIMENTAL_PluginMakeCredential()
+/// EXPERIMENTAL_PluginGetAssertion()
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct ExperimentalWebAuthnPluginOperationRequest {
+ pub window_handle: windows::Win32::Foundation::HWND,
+ pub transaction_id: windows_core::GUID,
+ pub request_signature_byte_count: u32,
+ pub request_signature_pointer: *mut u8,
+ pub encoded_request_byte_count: u32,
+ pub encoded_request_pointer: *mut u8,
+}
+
+/// Used as a response when creating and asserting credentials.
+/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_RESPONSE
+/// Header File Usage: EXPERIMENTAL_PluginMakeCredential()
+/// EXPERIMENTAL_PluginGetAssertion()
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct ExperimentalWebAuthnPluginOperationResponse {
+ pub encoded_response_byte_count: u32,
+ pub encoded_response_pointer: *mut u8,
+}
+
+/// Used to cancel an operation.
+/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST
+/// Header File Usage: EXPERIMENTAL_PluginCancelOperation()
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct ExperimentalWebAuthnPluginCancelOperationRequest {
+ pub transaction_id: windows_core::GUID,
+ pub request_signature_byte_count: u32,
+ pub request_signature_pointer: *mut u8,
+}
+
+#[interface("e6466e9a-b2f3-47c5-b88d-89bc14a8d998")]
+pub unsafe trait EXPERIMENTAL_IPluginAuthenticator: windows_core::IUnknown {
+ fn EXPERIMENTAL_PluginMakeCredential(
+ &self,
+ request: *const ExperimentalWebAuthnPluginOperationRequest,
+ response: *mut *mut ExperimentalWebAuthnPluginOperationResponse,
+ ) -> HRESULT;
+ fn EXPERIMENTAL_PluginGetAssertion(
+ &self,
+ request: *const ExperimentalWebAuthnPluginOperationRequest,
+ response: *mut *mut ExperimentalWebAuthnPluginOperationResponse,
+ ) -> HRESULT;
+ fn EXPERIMENTAL_PluginCancelOperation(
+ &self,
+ request: *const ExperimentalWebAuthnPluginCancelOperationRequest,
+ ) -> HRESULT;
+}
+
+#[implement(EXPERIMENTAL_IPluginAuthenticator)]
+pub struct PluginAuthenticatorComObject;
+
+#[implement(IClassFactory)]
+pub struct Factory;
+
+impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Impl {
+ unsafe fn EXPERIMENTAL_PluginMakeCredential(
+ &self,
+ request: *const ExperimentalWebAuthnPluginOperationRequest,
+ response: *mut *mut ExperimentalWebAuthnPluginOperationResponse,
+ ) -> HRESULT {
+ util::message("=== EXPERIMENTAL_PluginMakeCredential() called ===");
+
+ if request.is_null() {
+ util::message("ERROR: NULL request pointer");
+ return HRESULT(-1);
+ }
+
+ if response.is_null() {
+ util::message("ERROR: NULL response pointer");
+ return HRESULT(-1);
+ }
+
+ let req = &*request;
+ let transaction_id = format!("{:?}", req.transaction_id);
+
+ util::message(&format!("Transaction ID: {}", transaction_id));
+ util::message(&format!("Window Handle: {:?}", req.window_handle));
+ util::message(&format!("Request Signature Byte Count: {}", req.request_signature_byte_count));
+ util::message(&format!("Encoded Request Byte Count: {}", req.encoded_request_byte_count));
+
+ if req.encoded_request_byte_count == 0 || req.encoded_request_pointer.is_null() {
+ util::message("ERROR: No encoded request data provided");
+ return HRESULT(-1);
+ }
+
+ let encoded_request_slice = std::slice::from_raw_parts(
+ req.encoded_request_pointer,
+ req.encoded_request_byte_count as usize
+ );
+
+ util::message(&format!("Encoded request: {} bytes", encoded_request_slice.len()));
+
+ // Try to decode the request using Windows API
+ match decode_make_credential_request(encoded_request_slice) {
+ Ok(decoded_wrapper) => {
+ let decoded_request = decoded_wrapper.as_ref();
+ util::message("Successfully decoded make credential request using Windows API");
+
+ // Extract RP information
+ if decoded_request.pRpInformation.is_null() {
+ util::message("ERROR: RP information is null");
+ return HRESULT(-1);
+ }
+
+ let rp_info = &*decoded_request.pRpInformation;
+
+ let rpid = if rp_info.pwszId.is_null() {
+ util::message("ERROR: RP ID is null");
+ return HRESULT(-1);
+ } else {
+ match wstr_to_string(rp_info.pwszId) {
+ Ok(id) => id,
+ Err(e) => {
+ util::message(&format!("ERROR: Failed to decode RP ID: {}", e));
+ return HRESULT(-1);
+ }
+ }
+ };
+
+ let rp_name = if rp_info.pwszName.is_null() {
+ String::new()
+ } else {
+ wstr_to_string(rp_info.pwszName).unwrap_or_default()
+ };
+
+ // Extract user information
+ if decoded_request.pUserInformation.is_null() {
+ util::message("ERROR: User information is null");
+ return HRESULT(-1);
+ }
+
+ let user = &*decoded_request.pUserInformation;
+
+ let user_id = if user.pbId.is_null() || user.cbId == 0 {
+ util::message("ERROR: User ID is required for registration");
+ return HRESULT(-1);
+ } else {
+ let id_slice = std::slice::from_raw_parts(user.pbId, user.cbId as usize);
+ id_slice.to_vec()
+ };
+
+ let user_name = if user.pwszName.is_null() {
+ util::message("ERROR: User name is required for registration");
+ return HRESULT(-1);
+ } else {
+ match wstr_to_string(user.pwszName) {
+ Ok(name) => name,
+ Err(_) => {
+ util::message("ERROR: Failed to decode user name");
+ return HRESULT(-1);
+ }
+ }
+ };
+
+ let user_display_name = if user.pwszDisplayName.is_null() {
+ None
+ } else {
+ wstr_to_string(user.pwszDisplayName).ok()
+ };
+
+ let user_info = (user_id, user_name, user_display_name);
+
+ // Extract client data hash
+ let client_data_hash = if decoded_request.cbClientDataHash == 0 || decoded_request.pbClientDataHash.is_null() {
+ util::message("ERROR: Client data hash is required for registration");
+ return HRESULT(-1);
+ } else {
+ let hash_slice = std::slice::from_raw_parts(
+ decoded_request.pbClientDataHash,
+ decoded_request.cbClientDataHash as usize
+ );
+ hash_slice.to_vec()
+ };
+
+ // Extract RP ID raw bytes for authenticator data
+ let rpid_bytes = if decoded_request.cbRpId > 0 && !decoded_request.pbRpId.is_null() {
+ let rpid_slice = std::slice::from_raw_parts(
+ decoded_request.pbRpId,
+ decoded_request.cbRpId as usize
+ );
+ rpid_slice.to_vec()
+ } else {
+ rpid.as_bytes().to_vec()
+ };
+
+ // Extract supported algorithms
+ let supported_algorithms = if decoded_request.WebAuthNCredentialParameters.cCredentialParameters > 0 &&
+ !decoded_request.WebAuthNCredentialParameters.pCredentialParameters.is_null() {
+ let params_count = decoded_request.WebAuthNCredentialParameters.cCredentialParameters as usize;
+ let params_ptr = decoded_request.WebAuthNCredentialParameters.pCredentialParameters;
+
+ (0..params_count)
+ .map(|i| unsafe { &*params_ptr.add(i) }.lAlg)
+ .collect()
+ } else {
+ Vec::new()
+ };
+
+ // Create request context from properly decoded data
+ let mut request_context = RequestContext::default();
+ request_context.rpid = Some(rpid.clone());
+ request_context.user_id = Some(user_info.0);
+ request_context.user_name = Some(user_info.1);
+ request_context.user_display_name = user_info.2;
+ request_context.client_data_hash = Some(client_data_hash);
+ request_context.supported_algorithms = supported_algorithms;
+
+ util::message(&format!("Make credential request - RP: {}, User: {}",
+ rpid,
+ request_context.user_name.as_deref().unwrap_or("unknown")));
+
+ // Send registration request
+ if let Some(passkey_response) = send_registration_request(&rpid, &transaction_id, &request_context) {
+ util::message(&format!("Registration response received: {:?}", passkey_response));
+
+ // Create proper WebAuthn response from passkey_response
+ match passkey_response {
+ PasskeyResponse::RegistrationResponse { credential_id, attestation_object } => {
+ util::message("Creating WebAuthn make credential response");
+
+ match create_make_credential_response(credential_id, attestation_object) {
+ Ok(webauthn_response) => {
+ util::message("Successfully created WebAuthn response");
+ *response = webauthn_response;
+ HRESULT(0)
+ },
+ Err(e) => {
+ util::message(&format!("ERROR: Failed to create WebAuthn response: {}", e));
+ *response = ptr::null_mut();
+ HRESULT(-1)
+ }
+ }
+ },
+ PasskeyResponse::Error { message } => {
+ util::message(&format!("Registration request failed: {}", message));
+ *response = ptr::null_mut();
+ HRESULT(-1)
+ },
+ _ => {
+ util::message("ERROR: Unexpected response type for registration request");
+ *response = ptr::null_mut();
+ HRESULT(-1)
+ }
+ }
+ } else {
+ util::message("ERROR: No response from registration request");
+ *response = ptr::null_mut();
+ HRESULT(-1)
+ }
+ },
+ Err(e) => {
+ util::message(&format!("ERROR: Failed to decode make credential request: {}", e));
+ *response = ptr::null_mut();
+ HRESULT(-1)
+ }
+ }
+ }
+
+ unsafe fn EXPERIMENTAL_PluginGetAssertion(
+ &self,
+ request: *const ExperimentalWebAuthnPluginOperationRequest,
+ response: *mut *mut ExperimentalWebAuthnPluginOperationResponse,
+ ) -> HRESULT {
+ util::message("EXPERIMENTAL_PluginGetAssertion() called");
+
+ // Validate input parameters
+ if request.is_null() || response.is_null() {
+ util::message("Invalid parameters passed to EXPERIMENTAL_PluginGetAssertion");
+ return HRESULT(-1);
+ }
+
+ let req = &*request;
+ let transaction_id = format!("{:?}", req.transaction_id);
+
+ util::message(&format!("Get assertion request - Transaction: {}", transaction_id));
+
+ if req.encoded_request_byte_count == 0 || req.encoded_request_pointer.is_null() {
+ util::message("ERROR: No encoded request data provided");
+ *response = ptr::null_mut();
+ return HRESULT(-1);
+ }
+
+ let encoded_request_slice = std::slice::from_raw_parts(
+ req.encoded_request_pointer,
+ req.encoded_request_byte_count as usize
+ );
+
+ // Try to decode the request using Windows API
+ match decode_get_assertion_request(encoded_request_slice) {
+ Ok(decoded_wrapper) => {
+ let decoded_request = decoded_wrapper.as_ref();
+ util::message("Successfully decoded get assertion request using Windows API");
+
+ // Extract RP information
+ let rpid = if decoded_request.pwszRpId.is_null() {
+ util::message("ERROR: RP ID is null");
+ *response = ptr::null_mut();
+ return HRESULT(-1);
+ } else {
+ match wstr_to_string(decoded_request.pwszRpId) {
+ Ok(id) => id,
+ Err(e) => {
+ util::message(&format!("ERROR: Failed to decode RP ID: {}", e));
+ *response = ptr::null_mut();
+ return HRESULT(-1);
+ }
+ }
+ };
+
+ // Extract client data hash
+ let client_data_hash = if decoded_request.cbClientDataHash == 0 || decoded_request.pbClientDataHash.is_null() {
+ util::message("ERROR: Client data hash is required for assertion");
+ *response = ptr::null_mut();
+ return HRESULT(-1);
+ } else {
+ let hash_slice = std::slice::from_raw_parts(
+ decoded_request.pbClientDataHash,
+ decoded_request.cbClientDataHash as usize
+ );
+ hash_slice.to_vec()
+ };
+
+ // Create request context from properly decoded data
+ let mut request_context = RequestContext::default();
+ request_context.rpid = Some(rpid.clone());
+ request_context.client_data_hash = Some(client_data_hash);
+ // TODO: Extract allowed credentials from CredentialList if available
+
+ util::message(&format!("Get assertion request - RP: {}", rpid));
+
+ // Send assertion request
+ if let Some(passkey_response) = send_assertion_request(&rpid, &transaction_id, &request_context) {
+ util::message(&format!("Assertion response received: {:?}", passkey_response));
+
+ // Create proper WebAuthn response from passkey_response
+ match passkey_response {
+ PasskeyResponse::AssertionResponse { credential_id, authenticator_data, signature, user_handle } => {
+ util::message("Creating WebAuthn get assertion response");
+
+ match create_get_assertion_response(credential_id, authenticator_data, signature, user_handle) {
+ Ok(webauthn_response) => {
+ util::message("Successfully created WebAuthn assertion response");
+ *response = webauthn_response;
+ HRESULT(0)
+ },
+ Err(e) => {
+ util::message(&format!("ERROR: Failed to create WebAuthn assertion response: {}", e));
+ *response = ptr::null_mut();
+ HRESULT(-1)
+ }
+ }
+ },
+ PasskeyResponse::Error { message } => {
+ util::message(&format!("Assertion request failed: {}", message));
+ *response = ptr::null_mut();
+ HRESULT(-1)
+ },
+ _ => {
+ util::message("ERROR: Unexpected response type for assertion request");
+ *response = ptr::null_mut();
+ HRESULT(-1)
+ }
+ }
+ } else {
+ util::message("ERROR: No response from assertion request");
+ *response = ptr::null_mut();
+ HRESULT(-1)
+ }
+ },
+ Err(e) => {
+ util::message(&format!("ERROR: Failed to decode get assertion request: {}", e));
+ *response = ptr::null_mut();
+ HRESULT(-1)
+ }
+ }
+ }
+
+ unsafe fn EXPERIMENTAL_PluginCancelOperation(
+ &self,
+ request: *const ExperimentalWebAuthnPluginCancelOperationRequest,
+ ) -> HRESULT {
+ util::message("EXPERIMENTAL_PluginCancelOperation() called");
+ HRESULT(0)
+ }
+}
+
+impl IClassFactory_Impl for Factory_Impl {
+ fn CreateInstance(
+ &self,
+ outer: windows_core::Ref,
+ iid: *const windows_core::GUID,
+ object: *mut *mut core::ffi::c_void,
+ ) -> windows_core::Result<()> {
+ let unknown: IInspectable = PluginAuthenticatorComObject.into(); // TODO: IUnknown ?
+ unsafe { unknown.query(iid, object).ok() }
+ }
+
+ fn LockServer(&self, lock: windows_core::BOOL) -> windows_core::Result<()> {
+ Ok(())
+ }
+}
\ No newline at end of file
diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_registration.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_registration.rs
new file mode 100644
index 00000000000..6d82576fb63
--- /dev/null
+++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_registration.rs
@@ -0,0 +1,212 @@
+use std::ffi::OsString;
+use std::os::windows::ffi::OsStrExt;
+use std::ffi::c_uchar;
+use std::ptr;
+
+use windows::Win32::Foundation::*;
+use windows::Win32::System::Com::*;
+use windows_core::{s, HRESULT, PCWSTR, ComObjectInterface, GUID, HSTRING};
+
+use crate::utils::{WindowsString, delay_load, message};
+use crate::webauthn::*;
+use crate::com_provider;
+use hex;
+
+const AUTHENTICATOR_NAME: &str = "Bitwarden Desktop Authenticator";
+const CLSID: &str = "0f7dc5d9-69ce-4652-8572-6877fd695062";
+const RPID: &str = "bitwarden.com";
+
+/// Initializes the COM library for use on the calling thread,
+/// and registers + sets the security values.
+pub fn initialize_com_library() -> std::result::Result<(), String> {
+ let result = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) };
+
+ if result.is_err() {
+ return Err(format!(
+ "Error: couldn't initialize the COM library\n{}",
+ result.message()
+ ));
+ }
+
+ match unsafe {
+ CoInitializeSecurity(
+ None,
+ -1,
+ None,
+ None,
+ RPC_C_AUTHN_LEVEL_DEFAULT,
+ RPC_C_IMP_LEVEL_IMPERSONATE,
+ None,
+ EOAC_NONE,
+ None,
+ )
+ } {
+ Ok(_) => Ok(()),
+ Err(e) => Err(format!(
+ "Error: couldn't initialize COM security\n{}",
+ e.message()
+ )),
+ }
+}
+
+/// Registers the Bitwarden Plugin Authenticator COM library with Windows.
+pub fn register_com_library() -> std::result::Result<(), String> {
+ static FACTORY: windows_core::StaticComObject =
+ com_provider::Factory.into_static();
+ //let clsid: *const GUID = &GUID::from_u128(0xa98925d161f640de9327dc418fcb2ff4);
+ let clsid: *const GUID = &GUID::from_u128(0x0f7dc5d969ce465285726877fd695062);
+
+ match unsafe {
+ CoRegisterClassObject(
+ clsid,
+ FACTORY.as_interface_ref(),
+ //FACTORY.as_interface::(),
+ CLSCTX_LOCAL_SERVER,
+ REGCLS_MULTIPLEUSE,
+ )
+ } {
+ Ok(_) => Ok(()),
+ Err(e) => Err(format!(
+ "Error: couldn't register the COM library\n{}",
+ e.message()
+ )),
+ }
+}
+
+// testing wide encoding
+pub fn add_authenticator_using_wide_encoding() -> std::result::Result<(), String> {
+ // let (authenticator_name_pointer, authenticator_name_bytes) = String::from(AUTHENTICATOR_NAME).into_win_utf16_wide();
+ let mut authenticator_name: Vec = OsString::from(AUTHENTICATOR_NAME).encode_wide().collect();
+ //authenticator_name.push(0);
+ let authenticator_name_pointer = authenticator_name.as_mut_ptr();
+
+ // let (clsid_pointer, clsid_bytes) = String::from(CLSID).into_win_utf16_wide();
+ let mut clsid: Vec = OsString::from(CLSID).encode_wide().collect();
+ //clsid.push(0);
+ let clsid_pointer = clsid.as_mut_ptr();
+
+ // let (rpid_pointer, rpid_bytes) = String::from(RPID).into_win_utf16_wide();
+ let mut rpid: Vec = OsString::from(RPID).encode_wide().collect();
+ //rpid.push(0);
+ let rpid_pointer = rpid.as_mut_ptr();
+
+ // Example authenticator info blob
+ let cbor_authenticator_info = "A60182684649444F5F325F30684649444F5F325F310282637072666B686D61632D7365637265740350D548826E79B4DB40A3D811116F7E834904A362726BF5627570F5627576F5098168696E7465726E616C0A81A263616C672664747970656A7075626C69632D6B6579";
+ let mut authenticator_info_bytes = hex::decode(cbor_authenticator_info).unwrap();
+
+ let add_authenticator_options = ExperimentalWebAuthnPluginAddAuthenticatorOptions {
+ authenticator_name: authenticator_name_pointer,
+ plugin_clsid: clsid_pointer,
+ rpid: rpid_pointer,
+ light_theme_logo: ptr::null(),
+ dark_theme_logo: ptr::null(),
+ cbor_authenticator_info_byte_count: authenticator_info_bytes.len() as u32,
+ cbor_authenticator_info: authenticator_info_bytes.as_mut_ptr(),
+ };
+
+ let plugin_signing_public_key_byte_count: u32 = 0;
+ let mut plugin_signing_public_key: c_uchar = 0;
+ let plugin_signing_public_key_ptr = &mut plugin_signing_public_key;
+
+ let mut add_response = ExperimentalWebAuthnPluginAddAuthenticatorResponse {
+ plugin_operation_signing_key_byte_count: plugin_signing_public_key_byte_count,
+ plugin_operation_signing_key: plugin_signing_public_key_ptr,
+ };
+ let mut add_response_ptr: *mut ExperimentalWebAuthnPluginAddAuthenticatorResponse =
+ &mut add_response;
+
+ let result = unsafe {
+ delay_load::(
+ s!("webauthn.dll"),
+ s!("EXPERIMENTAL_WebAuthNPluginAddAuthenticator"),
+ )
+ };
+
+ match result {
+ Some(api) => {
+ let result = unsafe { api(&add_authenticator_options, &mut add_response_ptr) };
+
+ if result.is_err() {
+ return Err(format!(
+ "Error: Error response from EXPERIMENTAL_WebAuthNPluginAddAuthenticator()\n{}",
+ result.message()
+ ));
+ }
+
+ Ok(())
+ },
+ None => {
+ Err(String::from("Error: Can't complete add_authenticator(), as the function EXPERIMENTAL_WebAuthNPluginAddAuthenticator can't be found."))
+ }
+ }
+}
+
+/// Adds Bitwarden as a plugin authenticator.
+pub fn add_authenticator() -> std::result::Result<(), String> {
+ let authenticator_name: HSTRING = AUTHENTICATOR_NAME.into();
+ let authenticator_name_ptr = PCWSTR(authenticator_name.as_ptr()).as_ptr();
+
+ let clsid: HSTRING = format!("{{{}}}", CLSID).into();
+ let clsid_ptr = PCWSTR(clsid.as_ptr()).as_ptr();
+
+ let relying_party_id: HSTRING = RPID.into();
+ let relying_party_id_ptr = PCWSTR(relying_party_id.as_ptr()).as_ptr();
+
+ // let aaguid: HSTRING = format!("{{{}}}", AAGUID).into();
+ // let aaguid_ptr = PCWSTR(aaguid.as_ptr()).as_ptr();
+
+ // Example authenticator info blob
+ let cbor_authenticator_info = "A60182684649444F5F325F30684649444F5F325F310282637072666B686D61632D7365637265740350D548826E79B4DB40A3D811116F7E834904A362726BF5627570F5627576F5098168696E7465726E616C0A81A263616C672664747970656A7075626C69632D6B6579";
+ let mut authenticator_info_bytes = hex::decode(cbor_authenticator_info).unwrap();
+
+ let add_authenticator_options = ExperimentalWebAuthnPluginAddAuthenticatorOptions {
+ authenticator_name: authenticator_name_ptr,
+ plugin_clsid: clsid_ptr,
+ rpid: relying_party_id_ptr,
+ light_theme_logo: ptr::null(),
+ dark_theme_logo: ptr::null(),
+ cbor_authenticator_info_byte_count: authenticator_info_bytes.len() as u32,
+ cbor_authenticator_info: authenticator_info_bytes.as_mut_ptr(),
+ };
+
+ let plugin_signing_public_key_byte_count: u32 = 0;
+ let mut plugin_signing_public_key: c_uchar = 0;
+ let plugin_signing_public_key_ptr = &mut plugin_signing_public_key;
+
+ let mut add_response = ExperimentalWebAuthnPluginAddAuthenticatorResponse {
+ plugin_operation_signing_key_byte_count: plugin_signing_public_key_byte_count,
+ plugin_operation_signing_key: plugin_signing_public_key_ptr,
+ };
+ let mut add_response_ptr: *mut ExperimentalWebAuthnPluginAddAuthenticatorResponse =
+ &mut add_response;
+
+ let result = unsafe {
+ delay_load::(
+ s!("webauthn.dll"),
+ s!("EXPERIMENTAL_WebAuthNPluginAddAuthenticator"),
+ )
+ };
+
+ match result {
+ Some(api) => {
+ let result = unsafe { api(&add_authenticator_options, &mut add_response_ptr) };
+
+ if result.is_err() {
+ return Err(format!(
+ "Error: Error response from EXPERIMENTAL_WebAuthNPluginAddAuthenticator()\n{}",
+ result.message()
+ ));
+ }
+
+ Ok(())
+ },
+ None => {
+ Err(String::from("Error: Can't complete add_authenticator(), as the function EXPERIMENTAL_WebAuthNPluginAddAuthenticator can't be found."))
+ }
+ }
+}
+
+type EXPERIMENTAL_WebAuthNPluginAddAuthenticatorFnDeclaration = unsafe extern "cdecl" fn(
+ pPluginAddAuthenticatorOptions: *const ExperimentalWebAuthnPluginAddAuthenticatorOptions,
+ ppPluginAddAuthenticatorResponse: *mut *mut ExperimentalWebAuthnPluginAddAuthenticatorResponse,
+) -> HRESULT;
\ No newline at end of file
diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/ipc.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/ipc.rs
new file mode 100644
index 00000000000..0f1e7be18bd
--- /dev/null
+++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/ipc.rs
@@ -0,0 +1,65 @@
+use tokio::sync::{mpsc, oneshot};
+use std::sync::Mutex;
+
+use crate::types::*;
+use crate::utils::{self as util};
+
+/// Global channel sender for request notifications
+static REQUEST_SENDER: Mutex