From 1621b28406a74f2da6c0e7e813e61b52102e8a42 Mon Sep 17 00:00:00 2001 From: Isaiah Inuwa Date: Thu, 20 Nov 2025 10:08:12 -0600 Subject: [PATCH] Implement PluginAuthenticator::get_assertion --- .../src/assert.rs | 105 ++++++++++++++++-- .../windows_plugin_authenticator/src/lib.rs | 3 +- .../src/win_webauthn/com.rs | 47 ++++---- 3 files changed, 122 insertions(+), 33 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 cadff6d8e29..84a4f833f36 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs @@ -7,10 +7,6 @@ use std::{ }; use windows::core::{s, HRESULT}; -use crate::ipc2::{ - PasskeyAssertionRequest, PasskeyAssertionResponse, Position, TimedCallback, UserVerification, - WindowsProviderClient, -}; use crate::util::{delay_load, wstr_to_string}; use crate::webauthn::WEBAUTHN_CREDENTIAL_LIST; use crate::{ @@ -19,6 +15,13 @@ use crate::{ }, ipc2::PasskeyAssertionWithoutUserInterfaceRequest, }; +use crate::{ + ipc2::{ + PasskeyAssertionRequest, PasskeyAssertionResponse, Position, TimedCallback, + UserVerification, WindowsProviderClient, + }, + win_webauthn::{ErrorKind, HwndExt, PluginGetAssertionRequest, WinWebAuthnError}, +}; // Windows API types for WebAuthn (from webauthn.h.sample) #[repr(C)] @@ -161,12 +164,12 @@ fn send_assertion_request( } /// Creates a WebAuthn get assertion response from Bitwarden's assertion response -unsafe fn create_get_assertion_response( +fn create_get_assertion_response( credential_id: Vec, authenticator_data: Vec, signature: Vec, user_handle: Vec, -) -> std::result::Result<*mut WebAuthnPluginOperationResponse, HRESULT> { +) -> std::result::Result, WinWebAuthnError> { // Construct a CTAP2 response with the proper structure // Create CTAP2 GetAssertion response map according to CTAP2 specification @@ -223,15 +226,22 @@ unsafe fn create_get_assertion_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) { - tracing::debug!("ERROR: Failed to encode CBOR assertion response: {:?}", e); - return Err(HRESULT(-1)); + return Err(WinWebAuthnError::with_cause( + ErrorKind::Serialization, + "Failed to encode CBOR assertion response", + e, + )); } tracing::debug!("Formatted CBOR assertion response: {:?}", cbor_data); + Ok(cbor_data) +} - let response_len = cbor_data.len(); - +unsafe fn write_response( + cbor_data: &[u8], +) -> Result<*mut WebAuthnPluginOperationResponse, HRESULT> { // Allocate memory for the response data + let response_len = cbor_data.len(); let layout = Layout::from_size_align(response_len, 1).map_err(|_| HRESULT(-1))?; let response_ptr = alloc(layout); if response_ptr.is_null() { @@ -260,6 +270,71 @@ unsafe fn create_get_assertion_response( Ok(operation_response_ptr) } +pub fn get_assertion( + ipc_client: &WindowsProviderClient, + request: PluginGetAssertionRequest, +) -> Result, Box> { + // Extract RP information + let rp_id = request.rp_id().to_string(); + + // Extract client data hash + let client_data_hash = request.client_data_hash().to_vec(); + + // Extract user verification requirement from authenticator options + let user_verification = match request.authenticator_options().user_verification() { + Some(true) => UserVerification::Required, + Some(false) => UserVerification::Discouraged, + None => UserVerification::Preferred, + }; + + // Extract allowed credentials from credential list + let allowed_credential_ids: Vec> = request + .credential_list() + .iter() + .filter_map(|cred| cred.credential_id()) + .map(|id| id.to_vec()) + .collect(); + + let transaction_id = request.transaction_id.to_u128().to_le_bytes().to_vec(); + let client_pos = request + .window_handle + .center_position() + .unwrap_or((640, 480)); + + tracing::debug!( + "Get assertion request - RP: {}, Allowed credentials: {:?}", + rp_id, + allowed_credential_ids + ); + + // Send assertion request + let assertion_request = PasskeyAssertionRequest { + rp_id, + client_data_hash, + allowed_credentials: allowed_credential_ids, + user_verification, + window_xy: Position { + x: client_pos.0, + y: client_pos.1, + }, + context: transaction_id, + }; + let passkey_response = send_assertion_request(ipc_client, assertion_request) + .map_err(|err| format!("Failed to get assertion response from IPC channel: {err}"))?; + tracing::debug!("Assertion response received: {:?}", passkey_response); + + // Create proper WebAuthn response from passkey_response + tracing::debug!("Creating WebAuthn get assertion response"); + + let response = create_get_assertion_response( + passkey_response.credential_id, + passkey_response.authenticator_data, + passkey_response.signature, + passkey_response.user_handle, + )?; + Ok(response) +} + /// Implementation of PluginGetAssertion moved from com_provider.rs pub unsafe fn plugin_get_assertion( ipc_client: &WindowsProviderClient, @@ -377,6 +452,11 @@ pub unsafe fn plugin_get_assertion( passkey_response.signature, passkey_response.user_handle, ) + .map_err(|err| { + tracing::error!("Failed to encode WebAuthn assertion response as CBOR: {err}"); + HRESULT(-1) + }) + .and_then(|cbor| write_response(&cbor)) .map_err(|err| { tracing::error!("Failed to create WebAuthn assertion response: {err}"); HRESULT(-1) @@ -391,7 +471,7 @@ pub unsafe fn plugin_get_assertion( mod tests { use std::ptr::slice_from_raw_parts; - use super::create_get_assertion_response; + use super::{create_get_assertion_response, write_response}; #[test] fn test_create_native_assertion_response() { @@ -400,13 +480,14 @@ mod tests { let signature = vec![9, 10, 11, 12]; let user_handle = vec![13, 14, 15, 16]; let slice = unsafe { - let response = *create_get_assertion_response( + let cbor = create_get_assertion_response( credential_id, authenticator_data, signature, user_handle, ) .unwrap(); + let response = *write_response(&cbor).unwrap(); &*slice_from_raw_parts( response.encoded_response_pointer, response.encoded_response_byte_count as usize, 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 fd4d0cc40ea..6502849da36 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs @@ -113,7 +113,8 @@ impl PluginAuthenticator for BitwardenPluginAuthenticator { request: PluginGetAssertionRequest, ) -> Result, Box> { tracing::debug!("Received GetAssertion: {request:?}"); - Err(format!("GetAssertion not implemented").into()) + let client = self.get_client(); + assert::get_assertion(&client, request) } fn cancel_operation( diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/com.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/com.rs index 25e7632c839..1c809679fa4 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/com.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/com.rs @@ -134,35 +134,42 @@ impl IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Impl { response: *mut WEBAUTHN_PLUGIN_OPERATION_RESPONSE, ) -> HRESULT { tracing::debug!("GetAssertion called"); - if request.is_null() || response.is_null() { - tracing::warn!("GetAssertion called with invalid arguments"); + if response.is_null() { + tracing::warn!( + "GetAssertion called with null response pointer from Windows. Aborting request." + ); return E_INVALIDARG; } + let op_request_ptr = match NonNull::new(request as *mut WEBAUTHN_PLUGIN_OPERATION_REQUEST) { + Some(p) => p, + None => { + tracing::warn!( + "GetAssertion called with null request pointer from Windows. Aborting request." + ); + return E_INVALIDARG; + } + }; // TODO: verify request signature - let assertion_request: PluginGetAssertionRequest = - match NonNull::new(request as *mut WEBAUTHN_PLUGIN_OPERATION_REQUEST) - .map(PluginGetAssertionRequest::try_from) - { - Some(Ok(assertion_request)) => assertion_request, - Some(Err(err)) => { - tracing::error!("Could not deserialize GetAssertion request: {err}"); - return E_FAIL; - } - None => { - tracing::warn!("GetOperation received null request"); - return E_INVALIDARG; - } - }; + let assertion_request = match op_request_ptr.try_into() { + Ok(assertion_request) => assertion_request, + Err(err) => { + tracing::error!("Could not deserialize GetAssertion request: {err}"); + return E_FAIL; + } + }; match self.handler.get_assertion(assertion_request) { Ok(assertion_response) => { - let response = &mut *response; - response.cbEncodedResponse = assertion_response.len() as u32; + let (ptr, len) = ComBuffer::from_buffer(assertion_response); + (*response).cbEncodedResponse = len; + (*response).pbEncodedResponse = ptr; + /* std::ptr::copy_nonoverlapping( assertion_response.as_ptr(), - response.pbEncodedResponse, + (*response).pbEncodedResponse, assertion_response.len(), ); - tracing::error!("GetAssertion completed successfully"); + */ + tracing::debug!("GetAssertion completed successfully"); S_OK } Err(err) => {