1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-02 09:43:29 +00:00

Refactor to WindowsRequest instead of RequestContext

This commit is contained in:
Anders Åberg
2025-07-04 13:04:55 +02:00
parent c38c704f15
commit 9001967eea
5 changed files with 140 additions and 109 deletions

View File

@@ -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<Dec
Ok(DecodedGetAssertionRequest::new(pp_get_assertion_request, free_fn))
}
/// Context information parsed from the incoming request
/// Windows WebAuthn assertion request context
#[derive(Debug, Clone)]
pub struct RequestContext {
pub rpid: Option<String>,
pub struct WindowsAssertionRequest {
pub rpid: String,
pub client_data_hash: Vec<u8>,
pub allowed_credentials: Vec<Vec<u8>>,
pub user_verification: Option<UserVerificationRequirement>,
pub user_id: Option<Vec<u8>>,
pub user_name: Option<String>,
pub user_display_name: Option<String>,
pub client_data_hash: Option<Vec<u8>>,
pub supported_algorithms: Vec<i32>, // 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<u8>,
pub user_name: String,
pub user_display_name: Option<String>,
pub client_data_hash: Vec<u8>,
pub excluded_credentials: Vec<Vec<u8>>,
pub user_verification: UserVerificationRequirement,
pub supported_algorithms: Vec<i32>,
}
/// Parse allowed credentials from WEBAUTHN_CREDENTIAL_LIST
pub unsafe fn parse_credential_list(credential_list: &WEBAUTHN_CREDENTIAL_LIST) -> Vec<Vec<u8>> {
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<PasskeyResponse> {
// 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<PasskeyResponse> {
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));

View File

@@ -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, &registration_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

View File

@@ -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

View File

@@ -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<D
}
/// Helper for registration requests
pub fn send_registration_request(rpid: &str, transaction_id: &str, context: &RequestContext) -> Option<PasskeyResponse> {
// 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<PasskeyResponse> {
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));

View File

@@ -58,6 +58,7 @@ pub struct PasskeyRegistrationRequest {
pub client_data_hash: Vec<u8>,
pub user_verification: UserVerificationRequirement,
pub supported_algorithms: Vec<i32>, // COSE algorithm identifiers
pub excluded_credentials: Vec<Vec<u8>>, // Credentials to exclude from creation
}
/// Sync request structure