From 9001967eeacd6422ddb7787838da3d439fdb8837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85berg?= Date: Fri, 4 Jul 2025 13:04:55 +0200 Subject: [PATCH] Refactor to WindowsRequest instead of RequestContext --- .../src/assert.rs | 119 ++++++++++++------ .../src/com_provider.rs | 54 ++++---- .../windows_plugin_authenticator/src/lib.rs | 3 +- .../src/make_credential.rs | 72 ++++------- .../windows_plugin_authenticator/src/types.rs | 1 + 5 files changed, 140 insertions(+), 109 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 fe550984b26..59f769bf5d3 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs @@ -26,11 +26,21 @@ pub struct EXPERIMENTAL_WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST { 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_EX { + pub dwVersion: u32, + pub cbId: u32, + pub pbId: *const u8, + pub pwszCredentialType: *const u16, // LPCWSTR + pub dwTransports: u32, +} + #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct WEBAUTHN_CREDENTIAL_LIST { pub cCredentials: u32, - pub pCredentials: *const u8, // Placeholder + pub ppCredentials: *const *const WEBAUTHN_CREDENTIAL_EX, } // Windows API function signatures for decoding get assertion requests @@ -105,60 +115,89 @@ pub unsafe fn decode_get_assertion_request(encoded_request: &[u8]) -> Result, +pub struct WindowsAssertionRequest { + pub rpid: String, + pub client_data_hash: Vec, 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 + pub user_verification: UserVerificationRequirement, } -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(), - } +/// 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, +} + +/// Parse allowed credentials from WEBAUTHN_CREDENTIAL_LIST +pub unsafe fn parse_credential_list(credential_list: &WEBAUTHN_CREDENTIAL_LIST) -> Vec> { + let mut allowed_credentials = Vec::new(); + + if credential_list.cCredentials == 0 || credential_list.ppCredentials.is_null() { + util::message("No credentials in credential list"); + return allowed_credentials; } + + util::message(&format!("Parsing {} credentials from credential list", credential_list.cCredentials)); + + // ppCredentials is an array of pointers to WEBAUTHN_CREDENTIAL_EX + let credentials_array = std::slice::from_raw_parts( + credential_list.ppCredentials, + credential_list.cCredentials as usize + ); + + for (i, &credential_ptr) in credentials_array.iter().enumerate() { + if credential_ptr.is_null() { + util::message(&format!("WARNING: Credential {} is null, skipping", i)); + continue; + } + + let credential = &*credential_ptr; + + if credential.cbId == 0 || credential.pbId.is_null() { + util::message(&format!("WARNING: Credential {} has invalid ID, skipping", i)); + continue; + } + + // Extract credential ID bytes + let credential_id_slice = std::slice::from_raw_parts( + credential.pbId, + credential.cbId as usize + ); + + allowed_credentials.push(credential_id_slice.to_vec()); + util::message(&format!("Parsed credential {}: {} bytes", i, credential.cbId)); + } + + util::message(&format!("Successfully parsed {} allowed credentials", allowed_credentials.len())); + allowed_credentials } /// 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(), +pub fn send_assertion_request(transaction_id: &str, request: &WindowsAssertionRequest) -> Option { + let passkey_request = PasskeyAssertionRequest { + rp_id: request.rpid.clone(), transaction_id: transaction_id.to_string(), - client_data_hash, - allowed_credentials: context.allowed_credentials.clone(), - user_verification: context.user_verification.unwrap_or_default(), + client_data_hash: request.client_data_hash.clone(), + allowed_credentials: request.allowed_credentials.clone(), + user_verification: request.user_verification.clone(), }; util::message(&format!("Assertion request data - RP ID: {}, Client data hash: {} bytes, Allowed credentials: {}", - rpid, request.client_data_hash.len(), request.allowed_credentials.len())); + request.rpid, request.client_data_hash.len(), request.allowed_credentials.len())); - match serde_json::to_string(&request) { + match serde_json::to_string(&passkey_request) { Ok(request_json) => { util::message(&format!("Sending assertion request: {}", request_json)); - crate::ipc::send_passkey_request(RequestType::Assertion, request_json, rpid) + crate::ipc::send_passkey_request(RequestType::Assertion, request_json, &request.rpid) }, Err(e) => { util::message(&format!("ERROR: Failed to serialize assertion request: {}", e)); 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 aac6486be3a..159af6c9a87 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 @@ -4,8 +4,8 @@ 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}; +use crate::assert::{WindowsAssertionRequest, decode_get_assertion_request, create_get_assertion_response, send_assertion_request, parse_credential_list}; +use crate::make_credential::{WindowsRegistrationRequest, 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 @@ -224,22 +224,30 @@ impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Imp None }; - // 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; - request_context.user_verification = user_verification; + // Extract excluded credentials from credential list (for make credential, these are credentials to exclude) + let excluded_credentials = parse_credential_list(&decoded_request.CredentialList); + if !excluded_credentials.is_empty() { + util::message(&format!("Found {} excluded credentials for make credential", excluded_credentials.len())); + } + + // Create Windows registration request + let registration_request = WindowsRegistrationRequest { + rpid: rpid.clone(), + user_id: user_info.0, + user_name: user_info.1, + user_display_name: user_info.2, + client_data_hash, + excluded_credentials, + user_verification: user_verification.unwrap_or_default(), + supported_algorithms, + }; util::message(&format!("Make credential request - RP: {}, User: {}", rpid, - request_context.user_name.as_deref().unwrap_or("unknown"))); + registration_request.user_name)); // Send registration request - if let Some(passkey_response) = send_registration_request(&rpid, &transaction_id, &request_context) { + if let Some(passkey_response) = send_registration_request(&transaction_id, ®istration_request) { util::message(&format!("Registration response received: {:?}", passkey_response)); // Create proper WebAuthn response from passkey_response @@ -361,17 +369,21 @@ impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Imp None }; - // 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); - request_context.user_verification = user_verification; - // TODO: Extract allowed credentials from CredentialList if available + // Extract allowed credentials from credential list + let allowed_credentials = parse_credential_list(&decoded_request.CredentialList); - util::message(&format!("Get assertion request - RP: {}", rpid)); + // Create Windows assertion request + let assertion_request = WindowsAssertionRequest { + rpid: rpid.clone(), + client_data_hash, + allowed_credentials: allowed_credentials.clone(), + user_verification: user_verification.unwrap_or_default(), + }; + + util::message(&format!("Get assertion request - RP: {}, Allowed credentials: {}", rpid, allowed_credentials.len())); // Send assertion request - if let Some(passkey_response) = send_assertion_request(&rpid, &transaction_id, &request_context) { + if let Some(passkey_response) = send_assertion_request(&transaction_id, &assertion_request) { util::message(&format!("Assertion response received: {:?}", passkey_response)); // Create proper WebAuthn response from passkey_response 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 7cd8910c8c4..218d65a54db 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs @@ -23,7 +23,8 @@ mod com_buffer; // Re-export main functionality pub use sync::{send_sync_request, sync_credentials_to_windows, get_credentials_from_windows}; pub use ipc::{set_request_sender, send_passkey_request}; -pub use types::{PasskeyRequest, PasskeyResponse, SyncedCredential, RequestEvent, RequestType}; +pub use types::{PasskeyRequest, PasskeyResponse, SyncedCredential, RequestEvent, RequestType, UserVerificationRequirement}; +pub use assert::{WindowsAssertionRequest, WindowsRegistrationRequest}; pub use com_registration::{initialize_com_library, register_com_library, add_authenticator}; // Re-export utilities 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 10c15f4a01d..44cf8099318 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 @@ -6,7 +6,7 @@ use windows_core::{HRESULT, s}; use crate::types::*; use crate::utils::{self as util, delay_load}; use crate::com_provider::ExperimentalWebAuthnPluginOperationResponse; -use crate::assert::RequestContext; +use crate::assert::{WindowsRegistrationRequest, parse_credential_list}; // Windows API types for WebAuthn (from webauthn.h.sample) #[repr(C)] @@ -44,11 +44,21 @@ pub struct WEBAUTHN_COSE_CREDENTIAL_PARAMETERS { pub pCredentialParameters: *const WEBAUTHN_COSE_CREDENTIAL_PARAMETER, } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct WEBAUTHN_CREDENTIAL_EX { + pub dwVersion: u32, + pub cbId: u32, + pub pbId: *const u8, + pub pwszCredentialType: *const u16, // LPCWSTR + pub dwTransports: u32, +} + #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct WEBAUTHN_CREDENTIAL_LIST { pub cCredentials: u32, - pub pCredentials: *const u8, // Placeholder + pub ppCredentials: *const *const WEBAUTHN_CREDENTIAL_EX, } // Make Credential Request structure (from sample header) @@ -161,57 +171,25 @@ pub unsafe fn decode_make_credential_request(encoded_request: &[u8]) -> Result Option { - // Validate required fields - if rpid.is_empty() { - util::message("ERROR: RP ID is required but empty"); - return None; - } +pub fn send_registration_request(transaction_id: &str, request: &WindowsRegistrationRequest) -> Option { + util::message(&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())); - // Extract user ID from context - this is required for registration - let user_id = match &context.user_id { - Some(id) if !id.is_empty() => id.clone(), - _ => { - util::message("ERROR: User ID is required for registration but not provided"); - return None; - } - }; - - // Extract user name from context - this is required for registration - let user_name = match &context.user_name { - Some(name) if !name.is_empty() => name.clone(), - _ => { - util::message("ERROR: User name is required for registration but not provided"); - return None; - } - }; - - // 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 registration but not provided"); - return None; - } - }; - - util::message(&format!("Registration request data - RP ID: {}, User ID: {} bytes, User name: {}, Client data hash: {} bytes, Algorithms: {:?}", - rpid, user_id.len(), user_name, client_data_hash.len(), context.supported_algorithms)); - - let request = PasskeyRegistrationRequest { - rp_id: rpid.to_string(), + let passkey_request = PasskeyRegistrationRequest { + rp_id: request.rpid.clone(), transaction_id: transaction_id.to_string(), - user_id, - user_name, - client_data_hash, - user_verification: context.user_verification.unwrap_or_default(), - supported_algorithms: context.supported_algorithms.clone(), + user_id: request.user_id.clone(), + user_name: request.user_name.clone(), + client_data_hash: request.client_data_hash.clone(), + user_verification: request.user_verification.clone(), + supported_algorithms: request.supported_algorithms.clone(), + excluded_credentials: request.excluded_credentials.clone(), }; - match serde_json::to_string(&request) { + match serde_json::to_string(&passkey_request) { Ok(request_json) => { util::message(&format!("Sending registration request: {}", request_json)); - crate::ipc::send_passkey_request(RequestType::Registration, request_json, rpid) + crate::ipc::send_passkey_request(RequestType::Registration, request_json, &request.rpid) }, Err(e) => { util::message(&format!("ERROR: Failed to serialize registration request: {}", e)); diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/types.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/types.rs index ef48944c116..ed1ddfe0d68 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/types.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/types.rs @@ -58,6 +58,7 @@ pub struct PasskeyRegistrationRequest { pub client_data_hash: Vec, pub user_verification: UserVerificationRequirement, pub supported_algorithms: Vec, // COSE algorithm identifiers + pub excluded_credentials: Vec>, // Credentials to exclude from creation } /// Sync request structure