mirror of
https://github.com/bitwarden/browser
synced 2026-02-02 17:53:41 +00:00
Wire up Windows IPC for GetAssertion
This commit is contained in:
@@ -1,12 +1,20 @@
|
||||
use serde_json;
|
||||
use std::alloc::{alloc, Layout};
|
||||
use std::ptr;
|
||||
use std::{
|
||||
alloc::{alloc, Layout},
|
||||
ptr,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use windows_core::{s, HRESULT};
|
||||
|
||||
use crate::com_provider::{
|
||||
parse_credential_list, WebAuthnPluginOperationRequest, WebAuthnPluginOperationResponse,
|
||||
};
|
||||
use crate::types::*;
|
||||
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;
|
||||
|
||||
@@ -62,7 +70,7 @@ impl Drop for DecodedGetAssertionRequest {
|
||||
fn drop(&mut self) {
|
||||
if !self.ptr.is_null() {
|
||||
if let Some(free_fn) = self.free_fn {
|
||||
debug_log("Freeing decoded get assertion request");
|
||||
tracing::debug!("Freeing decoded get assertion request");
|
||||
unsafe {
|
||||
free_fn(self.ptr);
|
||||
}
|
||||
@@ -75,7 +83,7 @@ impl Drop for DecodedGetAssertionRequest {
|
||||
unsafe fn decode_get_assertion_request(
|
||||
encoded_request: &[u8],
|
||||
) -> Result<DecodedGetAssertionRequest, String> {
|
||||
debug_log("Attempting to decode get assertion request using Windows API");
|
||||
tracing::debug!("Attempting to decode get assertion request using Windows API");
|
||||
|
||||
// Load the Windows WebAuthn API function
|
||||
let decode_fn: Option<WebAuthNDecodeGetAssertionRequestFn> =
|
||||
@@ -122,38 +130,40 @@ pub struct WindowsAssertionRequest {
|
||||
|
||||
/// Helper for assertion requests
|
||||
fn send_assertion_request(
|
||||
ipc_client: &WindowsProviderClient,
|
||||
transaction_id: &str,
|
||||
request: &WindowsAssertionRequest,
|
||||
) -> Option<PasskeyResponse> {
|
||||
) -> Result<PasskeyAssertionResponse, String> {
|
||||
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(),
|
||||
// transaction_id: transaction_id.to_string(),
|
||||
client_data_hash: request.client_data_hash.clone(),
|
||||
allowed_credentials: request.allowed_credentials.clone(),
|
||||
user_verification: request.user_verification.clone(),
|
||||
user_verification,
|
||||
window_xy: Position { x: 400, y: 400 },
|
||||
};
|
||||
|
||||
debug_log(&format!(
|
||||
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,
|
||||
));
|
||||
);
|
||||
|
||||
match serde_json::to_string(&passkey_request) {
|
||||
Ok(request_json) => {
|
||||
debug_log(&format!("Sending assertion request: {}", request_json));
|
||||
crate::ipc::send_passkey_request(RequestType::Assertion, request_json, &request.rpid)
|
||||
}
|
||||
Err(e) => {
|
||||
debug_log(&format!(
|
||||
"ERROR: Failed to serialize assertion request: {}",
|
||||
e
|
||||
));
|
||||
None
|
||||
}
|
||||
}
|
||||
let request_json = serde_json::to_string(&passkey_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());
|
||||
callback
|
||||
.wait_for_response(Duration::from_secs(30))
|
||||
.map_err(|_| "Registration request timed out".to_string())?
|
||||
.map_err(|err| err.to_string())
|
||||
}
|
||||
|
||||
/// Creates a WebAuthn get assertion response from Bitwarden's assertion response
|
||||
@@ -264,15 +274,16 @@ unsafe fn create_get_assertion_response(
|
||||
|
||||
/// Implementation of PluginGetAssertion moved from com_provider.rs
|
||||
pub unsafe fn plugin_get_assertion(
|
||||
ipc_client: &WindowsProviderClient,
|
||||
request: *const WebAuthnPluginOperationRequest,
|
||||
response: *mut WebAuthnPluginOperationResponse,
|
||||
) -> HRESULT {
|
||||
debug_log("PluginGetAssertion() called");
|
||||
) -> Result<(), HRESULT> {
|
||||
tracing::debug!("PluginGetAssertion() called");
|
||||
|
||||
// Validate input parameters
|
||||
if request.is_null() || response.is_null() {
|
||||
debug_log("Invalid parameters passed to PluginGetAssertion");
|
||||
return HRESULT(-1);
|
||||
tracing::debug!("Invalid parameters passed to PluginGetAssertion");
|
||||
return Err(HRESULT(-1));
|
||||
}
|
||||
|
||||
let req = &*request;
|
||||
@@ -284,8 +295,8 @@ pub unsafe fn plugin_get_assertion(
|
||||
));
|
||||
|
||||
if req.encoded_request_byte_count == 0 || req.encoded_request_pointer.is_null() {
|
||||
debug_log("ERROR: No encoded request data provided");
|
||||
return HRESULT(-1);
|
||||
tracing::debug!("ERROR: No encoded request data provided");
|
||||
return Err(HRESULT(-1));
|
||||
}
|
||||
|
||||
let encoded_request_slice = std::slice::from_raw_parts(
|
||||
@@ -294,133 +305,93 @@ pub unsafe fn plugin_get_assertion(
|
||||
);
|
||||
|
||||
// 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();
|
||||
debug_log("Successfully decoded get assertion request using Windows API");
|
||||
let decoded_wrapper = decode_get_assertion_request(encoded_request_slice).map_err(|err| {
|
||||
tracing::debug!("Failed to decode get assertion request: {err}");
|
||||
HRESULT(-1)
|
||||
})?;
|
||||
let decoded_request = decoded_wrapper.as_ref();
|
||||
tracing::debug!("Successfully decoded get assertion request using Windows API");
|
||||
|
||||
// Extract RP information
|
||||
let rpid = if decoded_request.pwszRpId.is_null() {
|
||||
debug_log("ERROR: RP ID is null");
|
||||
return HRESULT(-1);
|
||||
} else {
|
||||
match wstr_to_string(decoded_request.pwszRpId) {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
debug_log(&format!("ERROR: Failed to decode RP ID: {}", e));
|
||||
return HRESULT(-1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Extract client data hash
|
||||
let client_data_hash = if decoded_request.cbClientDataHash == 0
|
||||
|| decoded_request.pbClientDataHash.is_null()
|
||||
{
|
||||
debug_log("ERROR: Client data hash is required for assertion");
|
||||
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 user verification requirement from authenticator options
|
||||
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
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// 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(),
|
||||
client_data_hash,
|
||||
allowed_credentials: allowed_credentials.clone(),
|
||||
user_verification: user_verification.unwrap_or_default(),
|
||||
};
|
||||
|
||||
debug_log(&format!(
|
||||
"Get assertion request - RP: {}, Allowed credentials: {:?}",
|
||||
rpid, allowed_credentials
|
||||
));
|
||||
|
||||
// Send assertion request
|
||||
if let Some(passkey_response) =
|
||||
send_assertion_request(&transaction_id, &assertion_request)
|
||||
{
|
||||
debug_log(&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,
|
||||
rp_id: _,
|
||||
client_data_hash: _,
|
||||
} => {
|
||||
debug_log("Creating WebAuthn get assertion response");
|
||||
|
||||
match create_get_assertion_response(
|
||||
credential_id,
|
||||
authenticator_data,
|
||||
signature,
|
||||
user_handle,
|
||||
) {
|
||||
Ok(webauthn_response) => {
|
||||
debug_log("Successfully created WebAuthn assertion response");
|
||||
(*response).encoded_response_byte_count =
|
||||
(*webauthn_response).encoded_response_byte_count;
|
||||
(*response).encoded_response_pointer =
|
||||
(*webauthn_response).encoded_response_pointer;
|
||||
HRESULT(0)
|
||||
}
|
||||
Err(e) => {
|
||||
debug_log(&format!(
|
||||
"ERROR: Failed to create WebAuthn assertion response: {}",
|
||||
e
|
||||
));
|
||||
HRESULT(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
PasskeyResponse::Error { message } => {
|
||||
debug_log(&format!("Assertion request failed: {}", message));
|
||||
HRESULT(-1)
|
||||
}
|
||||
_ => {
|
||||
debug_log("ERROR: Unexpected response type for assertion request");
|
||||
HRESULT(-1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug_log("ERROR: No response from assertion request");
|
||||
HRESULT(-1)
|
||||
// Extract RP information
|
||||
let rpid = if decoded_request.pwszRpId.is_null() {
|
||||
tracing::debug!("ERROR: RP ID is null");
|
||||
return Err(HRESULT(-1));
|
||||
} else {
|
||||
match wstr_to_string(decoded_request.pwszRpId) {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
tracing::debug!("ERROR: Failed to decode RP ID: {}", e);
|
||||
return Err(HRESULT(-1));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
debug_log(&format!(
|
||||
"ERROR: Failed to decode get assertion request: {}",
|
||||
e
|
||||
));
|
||||
HRESULT(-1)
|
||||
};
|
||||
|
||||
// Extract client data hash
|
||||
let client_data_hash =
|
||||
if decoded_request.cbClientDataHash == 0 || decoded_request.pbClientDataHash.is_null() {
|
||||
tracing::debug!("ERROR: Client data hash is required for assertion");
|
||||
return Err(HRESULT(-1));
|
||||
} else {
|
||||
let hash_slice = std::slice::from_raw_parts(
|
||||
decoded_request.pbClientDataHash,
|
||||
decoded_request.cbClientDataHash as usize,
|
||||
);
|
||||
hash_slice.to_vec()
|
||||
};
|
||||
|
||||
// Extract user verification requirement from authenticator options
|
||||
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
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// 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(),
|
||||
client_data_hash,
|
||||
allowed_credentials: allowed_credentials.clone(),
|
||||
user_verification: user_verification.unwrap_or_default(),
|
||||
};
|
||||
|
||||
debug_log(&format!(
|
||||
"Get assertion request - RP: {}, Allowed credentials: {:?}",
|
||||
rpid, allowed_credentials
|
||||
));
|
||||
|
||||
// Send assertion request
|
||||
let passkey_response = send_assertion_request(ipc_client, &transaction_id, &assertion_request)
|
||||
.map_err(|err| {
|
||||
tracing::error!("Assertion request failed: {err}");
|
||||
HRESULT(-1)
|
||||
})?;
|
||||
tracing::debug!("Assertion response received: {:?}", passkey_response);
|
||||
|
||||
// Create proper WebAuthn response from passkey_response
|
||||
tracing::debug!("Creating WebAuthn get assertion response");
|
||||
|
||||
let webauthn_response = create_get_assertion_response(
|
||||
passkey_response.credential_id,
|
||||
passkey_response.authenticator_data,
|
||||
passkey_response.signature,
|
||||
passkey_response.user_handle,
|
||||
)
|
||||
.map_err(|err| {
|
||||
format!("Failed to create WebAuthn assertion response: {err}");
|
||||
HRESULT(-1)
|
||||
})?;
|
||||
tracing::debug!("Successfully created WebAuthn assertion response");
|
||||
(*response).encoded_response_byte_count = (*webauthn_response).encoded_response_byte_count;
|
||||
(*response).encoded_response_pointer = (*webauthn_response).encoded_response_pointer;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -82,7 +82,7 @@ pub unsafe fn parse_credential_list(credential_list: &WEBAUTHN_CREDENTIAL_LIST)
|
||||
let mut allowed_credentials = Vec::new();
|
||||
|
||||
if credential_list.cCredentials == 0 || credential_list.ppCredentials.is_null() {
|
||||
debug_log("No credentials in credential list");
|
||||
tracing::debug!("No credentials in credential list");
|
||||
return allowed_credentials;
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ pub unsafe fn parse_credential_list(credential_list: &WEBAUTHN_CREDENTIAL_LIST)
|
||||
|
||||
for (i, &credential_ptr) in credentials_array.iter().enumerate() {
|
||||
if credential_ptr.is_null() {
|
||||
debug_log(&format!("WARNING: Credential {} is null, skipping", i));
|
||||
tracing::debug!("WARNING: Credential {} is null, skipping", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -145,11 +145,11 @@ impl IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Impl {
|
||||
request: *const WebAuthnPluginOperationRequest,
|
||||
response: *mut WebAuthnPluginOperationResponse,
|
||||
) -> HRESULT {
|
||||
debug_log("MakeCredential() called");
|
||||
debug_log("version2");
|
||||
tracing::debug!("MakeCredential() called");
|
||||
tracing::debug!("version2");
|
||||
// Convert to legacy format for internal processing
|
||||
if request.is_null() || response.is_null() {
|
||||
debug_log("MakeCredential: Invalid request or response pointers passed");
|
||||
tracing::debug!("MakeCredential: Invalid request or response pointers passed");
|
||||
return HRESULT(-1);
|
||||
}
|
||||
|
||||
@@ -164,23 +164,27 @@ impl IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Impl {
|
||||
request: *const WebAuthnPluginOperationRequest,
|
||||
response: *mut WebAuthnPluginOperationResponse,
|
||||
) -> HRESULT {
|
||||
debug_log("GetAssertion() called");
|
||||
tracing::debug!("GetAssertion() called");
|
||||
if request.is_null() || response.is_null() {
|
||||
return HRESULT(-1);
|
||||
}
|
||||
plugin_get_assertion(request, response)
|
||||
|
||||
match plugin_get_assertion(&self.client, request, response) {
|
||||
Ok(()) => S_OK,
|
||||
Err(err) => err,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn CancelOperation(
|
||||
&self,
|
||||
_request: *const WebAuthnPluginCancelOperationRequest,
|
||||
) -> HRESULT {
|
||||
debug_log("CancelOperation() called");
|
||||
tracing::debug!("CancelOperation() called");
|
||||
HRESULT(0)
|
||||
}
|
||||
|
||||
unsafe fn GetLockStatus(&self, lock_status: *mut PluginLockStatus) -> HRESULT {
|
||||
debug_log("GetLockStatus() called");
|
||||
tracing::debug!("GetLockStatus() called");
|
||||
if lock_status.is_null() {
|
||||
return HRESULT(-2147024809); // E_INVALIDARG
|
||||
}
|
||||
@@ -196,10 +200,10 @@ impl IClassFactory_Impl for Factory_Impl {
|
||||
iid: *const windows_core::GUID,
|
||||
object: *mut *mut core::ffi::c_void,
|
||||
) -> windows_core::Result<()> {
|
||||
debug_log("Creating COM server instance.");
|
||||
debug_log("Trying to connect to Bitwarden IPC");
|
||||
tracing::debug!("Creating COM server instance.");
|
||||
tracing::debug!("Trying to connect to Bitwarden IPC");
|
||||
let client = WindowsProviderClient::connect();
|
||||
debug_log("Connected to Bitwarden IPC");
|
||||
tracing::debug!("Connected to Bitwarden IPC");
|
||||
let unknown: IInspectable = PluginAuthenticatorComObject { client }.into(); // TODO: IUnknown ?
|
||||
unsafe { unknown.query(iid, object).ok() }
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ use super::{BitwardenError, Callback, Position, UserVerification};
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PasskeyAssertionRequest {
|
||||
rp_id: String,
|
||||
client_data_hash: Vec<u8>,
|
||||
user_verification: UserVerification,
|
||||
allowed_credentials: Vec<Vec<u8>>,
|
||||
window_xy: Position,
|
||||
//extension_input: Vec<u8>, TODO: Implement support for extensions
|
||||
pub rp_id: String,
|
||||
pub client_data_hash: Vec<u8>,
|
||||
pub user_verification: UserVerification,
|
||||
pub allowed_credentials: Vec<Vec<u8>>,
|
||||
pub window_xy: Position,
|
||||
// pub extension_input: Vec<u8>, TODO: Implement support for extensions
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -28,15 +28,15 @@ pub struct PasskeyAssertionWithoutUserInterfaceRequest {
|
||||
window_xy: Position,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PasskeyAssertionResponse {
|
||||
rp_id: String,
|
||||
user_handle: Vec<u8>,
|
||||
signature: Vec<u8>,
|
||||
client_data_hash: Vec<u8>,
|
||||
authenticator_data: Vec<u8>,
|
||||
credential_id: Vec<u8>,
|
||||
pub rp_id: String,
|
||||
pub user_handle: Vec<u8>,
|
||||
pub signature: Vec<u8>,
|
||||
pub client_data_hash: Vec<u8>,
|
||||
pub authenticator_data: Vec<u8>,
|
||||
pub credential_id: Vec<u8>,
|
||||
}
|
||||
|
||||
pub trait PreparePasskeyAssertionCallback: Send + Sync {
|
||||
|
||||
@@ -2,8 +2,12 @@ use std::{
|
||||
collections::HashMap,
|
||||
error::Error,
|
||||
fmt::Display,
|
||||
sync::{atomic::AtomicU32, Arc, Mutex, Once},
|
||||
time::Instant,
|
||||
sync::{
|
||||
atomic::AtomicU32,
|
||||
mpsc::{self, Receiver, Sender},
|
||||
Arc, Mutex, Once,
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use futures::FutureExt;
|
||||
@@ -308,3 +312,58 @@ impl WindowsProviderClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TimedCallback<T> {
|
||||
tx: Mutex<Option<Sender<Result<T, BitwardenError>>>>,
|
||||
rx: Mutex<Receiver<Result<T, BitwardenError>>>,
|
||||
}
|
||||
|
||||
impl<T> TimedCallback<T> {
|
||||
pub fn new() -> Self {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
Self {
|
||||
tx: Mutex::new(Some(tx)),
|
||||
rx: Mutex::new(rx),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait_for_response(
|
||||
&self,
|
||||
timeout: Duration,
|
||||
) -> Result<Result<T, BitwardenError>, mpsc::RecvTimeoutError> {
|
||||
self.rx.lock().unwrap().recv_timeout(timeout)
|
||||
}
|
||||
|
||||
fn send(&self, response: Result<T, BitwardenError>) {
|
||||
match self.tx.lock().unwrap().take() {
|
||||
Some(tx) => {
|
||||
if let Err(_) = tx.send(response) {
|
||||
tracing::error!("Windows provider channel closed before receiving IPC response from Electron")
|
||||
}
|
||||
}
|
||||
None => {
|
||||
tracing::error!("Callback channel used before response: multi-threading issue?");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PreparePasskeyRegistrationCallback for TimedCallback<PasskeyRegistrationResponse> {
|
||||
fn on_complete(&self, credential: PasskeyRegistrationResponse) {
|
||||
self.send(Ok(credential));
|
||||
}
|
||||
|
||||
fn on_error(&self, error: BitwardenError) {
|
||||
self.send(Err(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl PreparePasskeyAssertionCallback for TimedCallback<PasskeyAssertionResponse> {
|
||||
fn on_complete(&self, credential: PasskeyAssertionResponse) {
|
||||
self.send(Ok(credential));
|
||||
}
|
||||
|
||||
fn on_error(&self, error: BitwardenError) {
|
||||
self.send(Err(error))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ use crate::com_provider::{
|
||||
use crate::ipc2::{
|
||||
self, BitwardenError, PasskeyAssertionRequest, PasskeyAssertionResponse,
|
||||
PasskeyRegistrationRequest, PasskeyRegistrationResponse, Position,
|
||||
PreparePasskeyAssertionCallback, PreparePasskeyRegistrationCallback, UserVerification,
|
||||
WindowsProviderClient,
|
||||
PreparePasskeyAssertionCallback, PreparePasskeyRegistrationCallback, TimedCallback,
|
||||
UserVerification, WindowsProviderClient,
|
||||
};
|
||||
use crate::types::UserVerificationRequirement;
|
||||
use crate::util::{debug_log, delay_load, wstr_to_string, WindowsString};
|
||||
@@ -262,7 +262,7 @@ impl Drop for DecodedMakeCredentialRequest {
|
||||
fn drop(&mut self) {
|
||||
if !self.ptr.is_null() {
|
||||
if let Some(free_fn) = self.free_fn {
|
||||
debug_log("Freeing decoded make credential request");
|
||||
tracing::debug!("Freeing decoded make credential request");
|
||||
unsafe {
|
||||
free_fn(self.ptr as *mut WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST);
|
||||
}
|
||||
@@ -275,7 +275,7 @@ impl Drop for DecodedMakeCredentialRequest {
|
||||
unsafe fn decode_make_credential_request(
|
||||
encoded_request: &[u8],
|
||||
) -> Result<DecodedMakeCredentialRequest, String> {
|
||||
debug_log("Attempting to decode make credential request using Windows API");
|
||||
tracing::debug!("Attempting to decode make credential request using Windows API");
|
||||
|
||||
// Try to load the Windows API decode function
|
||||
let decode_fn = match delay_load::<WebAuthNDecodeMakeCredentialRequestFn>(
|
||||
@@ -318,7 +318,7 @@ unsafe fn decode_make_credential_request(
|
||||
}
|
||||
|
||||
if make_credential_request.is_null() {
|
||||
debug_log("ERROR: Windows API succeeded but returned null pointer");
|
||||
tracing::debug!("ERROR: Windows API succeeded but returned null pointer");
|
||||
return Err("Windows API returned null pointer".to_string());
|
||||
}
|
||||
|
||||
@@ -356,86 +356,13 @@ fn send_registration_request(
|
||||
|
||||
let request_json = serde_json::to_string(&passkey_request)
|
||||
.map_err(|err| format!("Failed to serialize registration request: {err}"))?;
|
||||
debug_log(&format!("Sending registration request: {}", request_json));
|
||||
let callback = Arc::new(Callback::new());
|
||||
tracing::debug!("Sending registration request: {}", request_json);
|
||||
let callback = Arc::new(TimedCallback::new());
|
||||
ipc_client.prepare_passkey_registration(passkey_request, callback.clone());
|
||||
callback
|
||||
.wait_for_response(Duration::from_secs(30))
|
||||
.map_err(|_| "Registration request timed out".to_string())?
|
||||
.map_err(|err| err.to_string())
|
||||
|
||||
/*
|
||||
{
|
||||
Ok(Ok(response)) => {
|
||||
tracing::debug!("Received registration response from Electron: {response:?}");
|
||||
Some(response)
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
tracing::error!("Registration request failed: {err}");
|
||||
None
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::error!("Timed out waiting for registration response");
|
||||
None
|
||||
}
|
||||
}
|
||||
*/
|
||||
// crate::ipc::send_passkey_request(RequestType::Registration, request_json, &request.rpid)
|
||||
}
|
||||
|
||||
struct Callback<T> {
|
||||
tx: Mutex<Option<Sender<Result<T, BitwardenError>>>>,
|
||||
rx: Mutex<Receiver<Result<T, BitwardenError>>>,
|
||||
}
|
||||
|
||||
impl<T> Callback<T> {
|
||||
fn new() -> Self {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
Self {
|
||||
tx: Mutex::new(Some(tx)),
|
||||
rx: Mutex::new(rx),
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_for_response(
|
||||
&self,
|
||||
timeout: Duration,
|
||||
) -> Result<Result<T, BitwardenError>, mpsc::RecvTimeoutError> {
|
||||
self.rx.lock().unwrap().recv_timeout(timeout)
|
||||
}
|
||||
|
||||
fn send(&self, response: Result<T, BitwardenError>) {
|
||||
match self.tx.lock().unwrap().take() {
|
||||
Some(tx) => {
|
||||
if let Err(_) = tx.send(response) {
|
||||
tracing::error!("Windows provider channel closed before receiving IPC response from Electron")
|
||||
}
|
||||
}
|
||||
None => {
|
||||
tracing::error!("Callback channel used before response: multi-threading issue?");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PreparePasskeyRegistrationCallback for Callback<PasskeyRegistrationResponse> {
|
||||
fn on_complete(&self, credential: PasskeyRegistrationResponse) {
|
||||
self.send(Ok(credential));
|
||||
}
|
||||
|
||||
fn on_error(&self, error: BitwardenError) {
|
||||
self.send(Err(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl PreparePasskeyAssertionCallback for Callback<PasskeyAssertionResponse> {
|
||||
fn on_complete(&self, credential: PasskeyAssertionResponse) {
|
||||
self.send(Ok(credential));
|
||||
}
|
||||
|
||||
fn on_error(&self, error: BitwardenError) {
|
||||
self.send(Err(error))
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a CTAP make credential response from Bitwarden's WebAuthn registration response
|
||||
@@ -560,7 +487,7 @@ unsafe fn create_make_credential_response(
|
||||
encoded_response_pointer: response_ptr,
|
||||
},
|
||||
);
|
||||
debug_log(&format!("CTAP-encoded attestation object: {response:?}"));
|
||||
tracing::debug!("CTAP-encoded attestation object: {response:?}");
|
||||
Ok(operation_response_ptr)
|
||||
*/
|
||||
}
|
||||
@@ -571,15 +498,15 @@ pub unsafe fn plugin_make_credential(
|
||||
request: *const WebAuthnPluginOperationRequest,
|
||||
response: *mut WebAuthnPluginOperationResponse,
|
||||
) -> Result<(), HRESULT> {
|
||||
debug_log("=== PluginMakeCredential() called ===");
|
||||
tracing::debug!("=== PluginMakeCredential() called ===");
|
||||
|
||||
if request.is_null() {
|
||||
debug_log("ERROR: NULL request pointer");
|
||||
tracing::debug!("ERROR: NULL request pointer");
|
||||
return Err(HRESULT(-1));
|
||||
}
|
||||
|
||||
if response.is_null() {
|
||||
debug_log("ERROR: NULL response pointer");
|
||||
tracing::debug!("ERROR: NULL response pointer");
|
||||
return Err(HRESULT(-1));
|
||||
}
|
||||
|
||||
@@ -587,7 +514,7 @@ pub unsafe fn plugin_make_credential(
|
||||
let transaction_id = format!("{:?}", req.transaction_id);
|
||||
|
||||
if req.encoded_request_byte_count == 0 || req.encoded_request_pointer.is_null() {
|
||||
debug_log("ERROR: No encoded request data provided");
|
||||
tracing::debug!("ERROR: No encoded request data provided");
|
||||
return Err(HRESULT(-1));
|
||||
}
|
||||
|
||||
@@ -609,24 +536,24 @@ pub unsafe fn plugin_make_credential(
|
||||
HRESULT(-1)
|
||||
})?;
|
||||
let decoded_request = decoded_wrapper.as_ref();
|
||||
debug_log("Successfully decoded make credential request using Windows API");
|
||||
tracing::debug!("Successfully decoded make credential request using Windows API");
|
||||
|
||||
// Extract RP information
|
||||
if decoded_request.pRpInformation.is_null() {
|
||||
debug_log("ERROR: RP information is null");
|
||||
tracing::debug!("ERROR: RP information is null");
|
||||
return Err(HRESULT(-1));
|
||||
}
|
||||
|
||||
let rp_info = &*decoded_request.pRpInformation;
|
||||
|
||||
let rpid = if rp_info.pwszId.is_null() {
|
||||
debug_log("ERROR: RP ID is null");
|
||||
tracing::debug!("ERROR: RP ID is null");
|
||||
return Err(HRESULT(-1));
|
||||
} else {
|
||||
match wstr_to_string(rp_info.pwszId) {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
debug_log(&format!("ERROR: Failed to decode RP ID: {}", e));
|
||||
tracing::debug!("ERROR: Failed to decode RP ID: {}", e);
|
||||
return Err(HRESULT(-1));
|
||||
}
|
||||
}
|
||||
@@ -640,14 +567,14 @@ pub unsafe fn plugin_make_credential(
|
||||
|
||||
// Extract user information
|
||||
if decoded_request.pUserInformation.is_null() {
|
||||
debug_log("ERROR: User information is null");
|
||||
tracing::debug!("ERROR: User information is null");
|
||||
return Err(HRESULT(-1));
|
||||
}
|
||||
|
||||
let user = &*decoded_request.pUserInformation;
|
||||
|
||||
let user_id = if user.pbId.is_null() || user.cbId == 0 {
|
||||
debug_log("ERROR: User ID is required for registration");
|
||||
tracing::debug!("ERROR: User ID is required for registration");
|
||||
return Err(HRESULT(-1));
|
||||
} else {
|
||||
let id_slice = std::slice::from_raw_parts(user.pbId, user.cbId as usize);
|
||||
@@ -655,13 +582,13 @@ pub unsafe fn plugin_make_credential(
|
||||
};
|
||||
|
||||
let user_name = if user.pwszName.is_null() {
|
||||
debug_log("ERROR: User name is required for registration");
|
||||
tracing::debug!("ERROR: User name is required for registration");
|
||||
return Err(HRESULT(-1));
|
||||
} else {
|
||||
match wstr_to_string(user.pwszName) {
|
||||
Ok(name) => name,
|
||||
Err(_) => {
|
||||
debug_log("ERROR: Failed to decode user name");
|
||||
tracing::debug!("ERROR: Failed to decode user name");
|
||||
return Err(HRESULT(-1));
|
||||
}
|
||||
}
|
||||
@@ -678,7 +605,7 @@ pub unsafe fn plugin_make_credential(
|
||||
// Extract client data hash
|
||||
let client_data_hash =
|
||||
if decoded_request.cbClientDataHash == 0 || decoded_request.pbClientDataHash.is_null() {
|
||||
debug_log("ERROR: Client data hash is required for registration");
|
||||
tracing::debug!("ERROR: Client data hash is required for registration");
|
||||
return Err(HRESULT(-1));
|
||||
} else {
|
||||
let hash_slice = std::slice::from_raw_parts(
|
||||
@@ -764,10 +691,10 @@ pub unsafe fn plugin_make_credential(
|
||||
));
|
||||
|
||||
// Create proper WebAuthn response from passkey_response
|
||||
debug_log("Creating WebAuthn make credential response");
|
||||
tracing::debug!("Creating WebAuthn make credential response");
|
||||
let mut webauthn_response =
|
||||
create_make_credential_response(passkey_response.attestation_object).map_err(|err| {
|
||||
debug_log(&format!("ERROR: Failed to create WebAuthn response: {err}"));
|
||||
tracing::debug!("ERROR: Failed to create WebAuthn response: {err}");
|
||||
HRESULT(-1)
|
||||
})?;
|
||||
debug_log(&format!(
|
||||
@@ -775,7 +702,7 @@ pub unsafe fn plugin_make_credential(
|
||||
));
|
||||
(*response).encoded_response_byte_count = webauthn_response.len() as u32;
|
||||
(*response).encoded_response_pointer = webauthn_response.as_mut_ptr();
|
||||
debug_log(&format!("Set pointer, returning HRESULT(0)"));
|
||||
tracing::debug!("Set pointer, returning HRESULT(0)");
|
||||
_ = ManuallyDrop::new(webauthn_response);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user