From 1094136290741d338577b76e39ef6a84e544dee1 Mon Sep 17 00:00:00 2001 From: Isaiah Inuwa Date: Sat, 8 Nov 2025 21:52:46 -0600 Subject: [PATCH] Sort of get window position from handle on Windows plugin --- .../src/assert.rs | 67 +++++--------- .../src/com_provider.rs | 15 +++- .../windows_plugin_authenticator/src/lib.rs | 2 - .../src/make_credential.rs | 88 ++++++------------- 4 files changed, 64 insertions(+), 108 deletions(-) diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs index 07786883d9a..ce8b21d2737 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs @@ -14,7 +14,6 @@ use crate::ipc2::{ PasskeyAssertionRequest, PasskeyAssertionResponse, Position, TimedCallback, UserVerification, WindowsProviderClient, }; -use crate::types::UserVerificationRequirement; use crate::util::{debug_log, delay_load, wstr_to_string}; use crate::webauthn::WEBAUTHN_CREDENTIAL_LIST; @@ -119,47 +118,23 @@ unsafe fn decode_get_assertion_request( )) } -/// Windows WebAuthn assertion request context -#[derive(Debug, Clone)] -pub struct WindowsAssertionRequest { - pub rpid: String, - pub client_data_hash: Vec, - pub allowed_credentials: Vec>, - pub user_verification: UserVerificationRequirement, -} - /// Helper for assertion requests fn send_assertion_request( ipc_client: &WindowsProviderClient, - transaction_id: &str, - request: &WindowsAssertionRequest, + request: PasskeyAssertionRequest, ) -> Result { - let user_verification = match request.user_verification { - UserVerificationRequirement::Discouraged => UserVerification::Discouraged, - UserVerificationRequirement::Preferred => UserVerification::Preferred, - UserVerificationRequirement::Required => UserVerification::Required, - }; - let passkey_request = PasskeyAssertionRequest { - rp_id: request.rpid.clone(), - // transaction_id: transaction_id.to_string(), - client_data_hash: request.client_data_hash.clone(), - allowed_credentials: request.allowed_credentials.clone(), - user_verification, - window_xy: Position { x: 400, y: 400 }, - }; - tracing::debug!( "Assertion request data - RP ID: {}, Client data hash: {} bytes, Allowed credentials: {:?}", - passkey_request.rp_id, - passkey_request.client_data_hash.len(), - passkey_request.allowed_credentials, + request.rp_id, + request.client_data_hash.len(), + request.allowed_credentials, ); - let request_json = serde_json::to_string(&passkey_request) + let request_json = serde_json::to_string(&request) .map_err(|err| format!("Failed to serialize assertion request: {err}"))?; tracing::debug!(?request_json, "Sending assertion request"); let callback = Arc::new(TimedCallback::new()); - ipc_client.prepare_passkey_assertion(passkey_request, callback.clone()); + ipc_client.prepare_passkey_assertion(request, callback.clone()); callback .wait_for_response(Duration::from_secs(30)) .map_err(|_| "Registration request timed out".to_string())? @@ -288,6 +263,7 @@ pub unsafe fn plugin_get_assertion( let req = &*request; let transaction_id = format!("{:?}", req.transaction_id); + let coords = req.window_coordinates().unwrap_or((400, 400)); debug_log(&format!( "Get assertion request - Transaction: {}", @@ -343,33 +319,38 @@ pub unsafe fn plugin_get_assertion( let user_verification = if !decoded_request.pAuthenticatorOptions.is_null() { let auth_options = &*decoded_request.pAuthenticatorOptions; match auth_options.user_verification { - 1 => Some(UserVerificationRequirement::Required), - -1 => Some(UserVerificationRequirement::Discouraged), - 0 | _ => Some(UserVerificationRequirement::Preferred), // Default or undefined + 1 => UserVerification::Required, + -1 => UserVerification::Discouraged, + 0 | _ => UserVerification::Preferred, // Default or undefined } } else { - None + UserVerification::Preferred // Default or undefined }; // Extract allowed credentials from credential list let allowed_credentials = parse_credential_list(&decoded_request.CredentialList); // Create Windows assertion request - let assertion_request = WindowsAssertionRequest { - rpid: rpid.clone(), + let assertion_request = PasskeyAssertionRequest { + rp_id: rpid.clone(), client_data_hash, allowed_credentials: allowed_credentials.clone(), - user_verification: user_verification.unwrap_or_default(), + window_xy: Position { + x: coords.0, + y: coords.1, + }, + user_verification, }; - debug_log(&format!( + tracing::debug!( "Get assertion request - RP: {}, Allowed credentials: {:?}", - rpid, allowed_credentials - )); + rpid, + allowed_credentials + ); // Send assertion request - let passkey_response = send_assertion_request(ipc_client, &transaction_id, &assertion_request) - .map_err(|err| { + let passkey_response = + send_assertion_request(ipc_client, assertion_request).map_err(|err| { tracing::error!("Assertion request failed: {err}"); HRESULT(-1) })?; 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 index 1efddab66a6..132f1a7a1f2 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_provider.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/com_provider.rs @@ -1,5 +1,6 @@ -use windows::Win32::Foundation::S_OK; +use windows::Win32::Foundation::{RECT, S_OK}; use windows::Win32::System::Com::*; +use windows::Win32::UI::WindowsAndMessaging::GetWindowRect; use windows_core::{implement, interface, IInspectable, IUnknown, Interface, HRESULT}; use crate::assert::plugin_get_assertion; @@ -39,6 +40,18 @@ pub struct WebAuthnPluginOperationRequest { pub encoded_request_pointer: *mut u8, } +impl WebAuthnPluginOperationRequest { + pub fn window_coordinates(&self) -> Result<(i32, i32), windows::core::Error> { + let mut window: RECT = RECT::default(); + unsafe { + GetWindowRect(self.window_handle, &mut window)?; + } + // TODO: This isn't quite right, but it's closer than what we had + let center_x = (window.right + window.left) / 2; + let center_y = (window.bottom + window.top) / 2; + Ok((center_x, center_y)) + } +} /// Used as a response when creating and asserting credentials. /// Header File Name: _WEBAUTHN_PLUGIN_OPERATION_RESPONSE /// Header File Usage: MakeCredential() diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs index fe217379d45..73c085ef83b 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs @@ -14,9 +14,7 @@ mod util; mod webauthn; // Re-export main functionality -pub use assert::WindowsAssertionRequest; pub use com_registration::{add_authenticator, initialize_com_library, register_com_library}; -pub use make_credential::WindowsRegistrationRequest; pub use types::UserVerificationRequirement; /// Handles initialization and registration for the Bitwarden desktop app as a diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/make_credential.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/make_credential.rs index 7d9df2330e3..a0411fd5ae8 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/make_credential.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/make_credential.rs @@ -1,43 +1,21 @@ use serde_json; -use std::alloc::{alloc, Layout}; use std::collections::HashMap; use std::mem::ManuallyDrop; -use std::sync::mpsc::Receiver; -use std::sync::Mutex; -use std::sync::{ - mpsc::{self, Sender}, - Arc, -}; +use std::ptr; +use std::sync::Arc; use std::time::Duration; -use std::{ptr, slice}; use windows_core::{s, HRESULT}; use crate::com_provider::{ parse_credential_list, WebAuthnPluginOperationRequest, WebAuthnPluginOperationResponse, }; use crate::ipc2::{ - self, BitwardenError, PasskeyAssertionRequest, PasskeyAssertionResponse, - PasskeyRegistrationRequest, PasskeyRegistrationResponse, Position, - PreparePasskeyAssertionCallback, PreparePasskeyRegistrationCallback, TimedCallback, + PasskeyRegistrationRequest, PasskeyRegistrationResponse, Position, TimedCallback, UserVerification, WindowsProviderClient, }; -use crate::types::UserVerificationRequirement; use crate::util::{debug_log, delay_load, wstr_to_string, WindowsString}; use crate::webauthn::WEBAUTHN_CREDENTIAL_LIST; -/// Windows WebAuthn registration request context -#[derive(Debug, Clone)] -pub struct WindowsRegistrationRequest { - pub rpid: String, - pub user_id: Vec, - pub user_name: String, - pub user_display_name: Option, - pub client_data_hash: Vec, - pub excluded_credentials: Vec>, - pub user_verification: UserVerificationRequirement, - pub supported_algorithms: Vec, -} - // Windows API types for WebAuthn (from webauthn.h.sample) #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -331,34 +309,16 @@ unsafe fn decode_make_credential_request( /// Helper for registration requests fn send_registration_request( ipc_client: &WindowsProviderClient, - transaction_id: &str, - request: &WindowsRegistrationRequest, + request: PasskeyRegistrationRequest, ) -> Result { debug_log(&format!("Registration request data - RP ID: {}, User ID: {} bytes, User name: {}, Client data hash: {} bytes, Algorithms: {:?}, Excluded credentials: {}", - request.rpid, request.user_id.len(), request.user_name, request.client_data_hash.len(), request.supported_algorithms, request.excluded_credentials.len())); + request.rp_id, request.user_handle.len(), request.user_name, request.client_data_hash.len(), request.supported_algorithms, request.excluded_credentials.len())); - let user_verification = match request.user_verification { - UserVerificationRequirement::Discouraged => UserVerification::Discouraged, - UserVerificationRequirement::Preferred => UserVerification::Preferred, - UserVerificationRequirement::Required => UserVerification::Required, - }; - let passkey_request = PasskeyRegistrationRequest { - rp_id: request.rpid.clone(), - // transaction_id: transaction_id.to_string(), - user_handle: request.user_id.clone(), - user_name: request.user_name.clone(), - client_data_hash: request.client_data_hash.clone(), - user_verification, - window_xy: Position { x: 400, y: 400 }, // TODO: Get actual window position - supported_algorithms: request.supported_algorithms.clone(), - excluded_credentials: request.excluded_credentials.clone(), - }; - - let request_json = serde_json::to_string(&passkey_request) + let request_json = serde_json::to_string(&request) .map_err(|err| format!("Failed to serialize registration request: {err}"))?; tracing::debug!("Sending registration request: {}", request_json); let callback = Arc::new(TimedCallback::new()); - ipc_client.prepare_passkey_registration(passkey_request, callback.clone()); + ipc_client.prepare_passkey_registration(request, callback.clone()); callback .wait_for_response(Duration::from_secs(30)) .map_err(|_| "Registration request timed out".to_string())? @@ -513,6 +473,8 @@ pub unsafe fn plugin_make_credential( let req = &*request; let transaction_id = format!("{:?}", req.transaction_id); + let coords = req.window_coordinates().unwrap_or((400, 400)); + if req.encoded_request_byte_count == 0 || req.encoded_request_pointer.is_null() { tracing::error!("No encoded request data provided"); return Err(HRESULT(-1)); @@ -643,12 +605,12 @@ pub unsafe fn plugin_make_credential( let user_verification = if !decoded_request.pAuthenticatorOptions.is_null() { let auth_options = &*decoded_request.pAuthenticatorOptions; match auth_options.user_verification { - 1 => Some(UserVerificationRequirement::Required), - -1 => Some(UserVerificationRequirement::Discouraged), - 0 | _ => Some(UserVerificationRequirement::Preferred), // Default or undefined + 1 => UserVerification::Required, + -1 => UserVerification::Discouraged, + 0 | _ => UserVerification::Preferred, // Default or undefined } } else { - None + UserVerification::Preferred // Default or undefined }; // Extract excluded credentials from credential list @@ -661,15 +623,19 @@ pub unsafe fn plugin_make_credential( } // Create Windows registration request - let registration_request = WindowsRegistrationRequest { - rpid: rpid.clone(), - user_id: user_info.0, + let registration_request = PasskeyRegistrationRequest { + rp_id: rpid.clone(), + user_handle: user_info.0, user_name: user_info.1, - user_display_name: user_info.2, + // user_display_name: user_info.2, client_data_hash, excluded_credentials, - user_verification: user_verification.unwrap_or_default(), + user_verification: user_verification, supported_algorithms, + window_xy: Position { + x: coords.0, + y: coords.1, + }, }; debug_log(&format!( @@ -679,12 +645,10 @@ pub unsafe fn plugin_make_credential( // Send registration request let passkey_response = - send_registration_request(ipc_client, &transaction_id, ®istration_request).map_err( - |err| { - tracing::error!("Registration request failed: {err}"); - HRESULT(-1) - }, - )?; + send_registration_request(ipc_client, registration_request).map_err(|err| { + tracing::error!("Registration request failed: {err}"); + HRESULT(-1) + })?; debug_log(&format!( "Registration response received: {:?}", passkey_response