mirror of
https://github.com/bitwarden/browser
synced 2026-02-01 09:13:54 +00:00
Update windows_plugin_authenticator to stable interface
This commit is contained in:
@@ -37,13 +37,13 @@ pub struct ExperimentalWebAuthnPluginOperationRequest {
|
||||
pub encoded_request_pointer: *mut u8,
|
||||
}
|
||||
|
||||
/// Used when creating and asserting credentials with EXPERIMENTAL2 interface.
|
||||
/// Header File Name: _EXPERIMENTAL2_WEBAUTHN_PLUGIN_OPERATION_REQUEST
|
||||
/// Header File Usage: EXPERIMENTAL_MakeCredential()
|
||||
/// EXPERIMENTAL_GetAssertion()
|
||||
/// Used when creating and asserting credentials with stable interface.
|
||||
/// Header File Name: _WEBAUTHN_PLUGIN_OPERATION_REQUEST
|
||||
/// Header File Usage: MakeCredential()
|
||||
/// GetAssertion()
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Experimental2WebAuthnPluginOperationRequest {
|
||||
pub struct WebAuthnPluginOperationRequest {
|
||||
pub window_handle: windows::Win32::Foundation::HWND,
|
||||
pub transaction_id: windows_core::GUID,
|
||||
pub request_signature_byte_count: u32,
|
||||
@@ -53,74 +53,45 @@ pub struct Experimental2WebAuthnPluginOperationRequest {
|
||||
pub encoded_request_pointer: *mut u8,
|
||||
}
|
||||
|
||||
|
||||
/// Used as a response when creating and asserting credentials.
|
||||
/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_RESPONSE
|
||||
/// Header File Usage: EXPERIMENTAL_PluginMakeCredential()
|
||||
/// EXPERIMENTAL_PluginGetAssertion()
|
||||
/// Header File Name: _WEBAUTHN_PLUGIN_OPERATION_RESPONSE
|
||||
/// Header File Usage: MakeCredential()
|
||||
/// GetAssertion()
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ExperimentalWebAuthnPluginOperationResponse {
|
||||
pub struct WebAuthnPluginOperationResponse {
|
||||
pub encoded_response_byte_count: u32,
|
||||
pub encoded_response_pointer: *mut u8,
|
||||
}
|
||||
|
||||
/// Used to cancel an operation.
|
||||
/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST
|
||||
/// Header File Usage: EXPERIMENTAL_PluginCancelOperation()
|
||||
/// Header File Name: _WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST
|
||||
/// Header File Usage: CancelOperation()
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ExperimentalWebAuthnPluginCancelOperationRequest {
|
||||
pub struct WebAuthnPluginCancelOperationRequest {
|
||||
pub transaction_id: windows_core::GUID,
|
||||
pub request_signature_byte_count: u32,
|
||||
pub request_signature_pointer: *mut u8,
|
||||
}
|
||||
|
||||
#[interface("e6466e9a-b2f3-47c5-b88d-89bc14a8d998")]
|
||||
pub unsafe trait EXPERIMENTAL_IPluginAuthenticator: windows_core::IUnknown {
|
||||
fn EXPERIMENTAL_PluginMakeCredential(
|
||||
&self,
|
||||
request: *const ExperimentalWebAuthnPluginOperationRequest,
|
||||
response: *mut *mut ExperimentalWebAuthnPluginOperationResponse,
|
||||
) -> HRESULT;
|
||||
fn EXPERIMENTAL_PluginGetAssertion(
|
||||
&self,
|
||||
request: *const ExperimentalWebAuthnPluginOperationRequest,
|
||||
response: *mut *mut ExperimentalWebAuthnPluginOperationResponse,
|
||||
) -> HRESULT;
|
||||
fn EXPERIMENTAL_PluginCancelOperation(
|
||||
&self,
|
||||
request: *const ExperimentalWebAuthnPluginCancelOperationRequest,
|
||||
) -> HRESULT;
|
||||
fn EXPERIMENTAL_GetLockStatus(
|
||||
&self,
|
||||
lock_status: *mut PluginLockStatus,
|
||||
) -> HRESULT;
|
||||
}
|
||||
|
||||
// Stable IPluginAuthenticator interface
|
||||
#[interface("d26bcf6f-b54c-43ff-9f06-d5bf148625f7")]
|
||||
pub unsafe trait EXPERIMENTAL2_IPluginAuthenticator: windows_core::IUnknown {
|
||||
fn EXPERIMENTAL_MakeCredential(
|
||||
pub unsafe trait IPluginAuthenticator: windows_core::IUnknown {
|
||||
fn MakeCredential(
|
||||
&self,
|
||||
request: *const Experimental2WebAuthnPluginOperationRequest,
|
||||
response: *mut *mut ExperimentalWebAuthnPluginOperationResponse,
|
||||
request: *const WebAuthnPluginOperationRequest,
|
||||
response: *mut WebAuthnPluginOperationResponse,
|
||||
) -> HRESULT;
|
||||
fn EXPERIMENTAL_GetAssertion(
|
||||
fn GetAssertion(
|
||||
&self,
|
||||
request: *const Experimental2WebAuthnPluginOperationRequest,
|
||||
response: *mut *mut ExperimentalWebAuthnPluginOperationResponse,
|
||||
) -> HRESULT;
|
||||
fn EXPERIMENTAL_CancelOperation(
|
||||
&self,
|
||||
request: *const ExperimentalWebAuthnPluginCancelOperationRequest,
|
||||
) -> HRESULT;
|
||||
fn EXPERIMENTAL_GetLockStatus(
|
||||
&self,
|
||||
lock_status: *mut PluginLockStatus,
|
||||
request: *const WebAuthnPluginOperationRequest,
|
||||
response: *mut WebAuthnPluginOperationResponse,
|
||||
) -> HRESULT;
|
||||
fn CancelOperation(&self, request: *const WebAuthnPluginCancelOperationRequest) -> HRESULT;
|
||||
fn GetLockStatus(&self, lock_status: *mut PluginLockStatus) -> HRESULT;
|
||||
}
|
||||
|
||||
|
||||
pub unsafe fn parse_credential_list(credential_list: &WEBAUTHN_CREDENTIAL_LIST) -> Vec<Vec<u8>> {
|
||||
let mut allowed_credentials = Vec::new();
|
||||
|
||||
@@ -174,57 +145,20 @@ pub unsafe fn parse_credential_list(credential_list: &WEBAUTHN_CREDENTIAL_LIST)
|
||||
allowed_credentials
|
||||
}
|
||||
|
||||
#[implement(EXPERIMENTAL_IPluginAuthenticator, EXPERIMENTAL2_IPluginAuthenticator)]
|
||||
#[implement(IPluginAuthenticator)]
|
||||
pub struct PluginAuthenticatorComObject;
|
||||
|
||||
#[implement(IClassFactory)]
|
||||
pub struct Factory;
|
||||
|
||||
impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Impl {
|
||||
unsafe fn EXPERIMENTAL_PluginMakeCredential(
|
||||
impl IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Impl {
|
||||
unsafe fn MakeCredential(
|
||||
&self,
|
||||
request: *const ExperimentalWebAuthnPluginOperationRequest,
|
||||
response: *mut *mut ExperimentalWebAuthnPluginOperationResponse,
|
||||
request: *const WebAuthnPluginOperationRequest,
|
||||
response: *mut WebAuthnPluginOperationResponse,
|
||||
) -> HRESULT {
|
||||
experimental_plugin_make_credential(request, response)
|
||||
}
|
||||
|
||||
unsafe fn EXPERIMENTAL_PluginGetAssertion(
|
||||
&self,
|
||||
request: *const ExperimentalWebAuthnPluginOperationRequest,
|
||||
response: *mut *mut ExperimentalWebAuthnPluginOperationResponse,
|
||||
) -> HRESULT {
|
||||
experimental_plugin_get_assertion(request, response)
|
||||
}
|
||||
|
||||
unsafe fn EXPERIMENTAL_PluginCancelOperation(
|
||||
&self,
|
||||
_request: *const ExperimentalWebAuthnPluginCancelOperationRequest,
|
||||
) -> HRESULT {
|
||||
debug_log("EXPERIMENTAL_PluginCancelOperation() called");
|
||||
HRESULT(0)
|
||||
}
|
||||
|
||||
unsafe fn EXPERIMENTAL_GetLockStatus(
|
||||
&self,
|
||||
lock_status: *mut PluginLockStatus,
|
||||
) -> HRESULT {
|
||||
debug_log("EXPERIMENTAL_GetLockStatus() called");
|
||||
if lock_status.is_null() {
|
||||
return HRESULT(-2147024809); // E_INVALIDARG
|
||||
}
|
||||
*lock_status = PluginLockStatus::PluginUnlocked;
|
||||
HRESULT(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl EXPERIMENTAL2_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Impl {
|
||||
unsafe fn EXPERIMENTAL_MakeCredential(
|
||||
&self,
|
||||
request: *const Experimental2WebAuthnPluginOperationRequest,
|
||||
response: *mut *mut ExperimentalWebAuthnPluginOperationResponse,
|
||||
) -> HRESULT {
|
||||
debug_log("EXPERIMENTAL2_MakeCredential() called");
|
||||
debug_log("MakeCredential() called");
|
||||
// Convert to legacy format for internal processing
|
||||
let legacy_request = ExperimentalWebAuthnPluginOperationRequest {
|
||||
window_handle: (*request).window_handle,
|
||||
transaction_id: (*request).transaction_id,
|
||||
@@ -233,15 +167,27 @@ impl EXPERIMENTAL2_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Im
|
||||
encoded_request_byte_count: (*request).encoded_request_byte_count,
|
||||
encoded_request_pointer: (*request).encoded_request_pointer,
|
||||
};
|
||||
experimental_plugin_make_credential(&legacy_request, response)
|
||||
|
||||
let mut legacy_response: *mut ExperimentalWebAuthnPluginOperationResponse = ptr::null_mut();
|
||||
let result = experimental_plugin_make_credential(&legacy_request, &mut legacy_response);
|
||||
|
||||
if result.is_ok() && !legacy_response.is_null() {
|
||||
// Copy response data
|
||||
(*response).encoded_response_byte_count =
|
||||
(*legacy_response).encoded_response_byte_count;
|
||||
(*response).encoded_response_pointer = (*legacy_response).encoded_response_pointer;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
unsafe fn EXPERIMENTAL_GetAssertion(
|
||||
unsafe fn GetAssertion(
|
||||
&self,
|
||||
request: *const Experimental2WebAuthnPluginOperationRequest,
|
||||
response: *mut *mut ExperimentalWebAuthnPluginOperationResponse,
|
||||
request: *const WebAuthnPluginOperationRequest,
|
||||
response: *mut WebAuthnPluginOperationResponse,
|
||||
) -> HRESULT {
|
||||
debug_log("EXPERIMENTAL2_GetAssertion() called");
|
||||
debug_log("GetAssertion() called");
|
||||
// Convert to legacy format for internal processing
|
||||
let legacy_request = ExperimentalWebAuthnPluginOperationRequest {
|
||||
window_handle: (*request).window_handle,
|
||||
transaction_id: (*request).transaction_id,
|
||||
@@ -250,22 +196,30 @@ impl EXPERIMENTAL2_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Im
|
||||
encoded_request_byte_count: (*request).encoded_request_byte_count,
|
||||
encoded_request_pointer: (*request).encoded_request_pointer,
|
||||
};
|
||||
experimental_plugin_get_assertion(&legacy_request, response)
|
||||
|
||||
let mut legacy_response: *mut ExperimentalWebAuthnPluginOperationResponse = ptr::null_mut();
|
||||
let result = experimental_plugin_get_assertion(&legacy_request, &mut legacy_response);
|
||||
|
||||
if result.is_ok() && !legacy_response.is_null() {
|
||||
// Copy response data
|
||||
(*response).encoded_response_byte_count =
|
||||
(*legacy_response).encoded_response_byte_count;
|
||||
(*response).encoded_response_pointer = (*legacy_response).encoded_response_pointer;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
unsafe fn EXPERIMENTAL_CancelOperation(
|
||||
unsafe fn CancelOperation(
|
||||
&self,
|
||||
_request: *const ExperimentalWebAuthnPluginCancelOperationRequest,
|
||||
_request: *const WebAuthnPluginCancelOperationRequest,
|
||||
) -> HRESULT {
|
||||
debug_log("EXPERIMENTAL2_CancelOperation() called");
|
||||
debug_log("CancelOperation() called");
|
||||
HRESULT(0)
|
||||
}
|
||||
|
||||
unsafe fn EXPERIMENTAL_GetLockStatus(
|
||||
&self,
|
||||
lock_status: *mut PluginLockStatus,
|
||||
) -> HRESULT {
|
||||
debug_log("EXPERIMENTAL2_GetLockStatus() called");
|
||||
unsafe fn GetLockStatus(&self, lock_status: *mut PluginLockStatus) -> HRESULT {
|
||||
debug_log("GetLockStatus() called");
|
||||
if lock_status.is_null() {
|
||||
return HRESULT(-2147024809); // E_INVALIDARG
|
||||
}
|
||||
@@ -274,7 +228,6 @@ impl EXPERIMENTAL2_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Im
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl IClassFactory_Impl for Factory_Impl {
|
||||
fn CreateInstance(
|
||||
&self,
|
||||
|
||||
@@ -34,10 +34,10 @@ fn parse_uuid_to_bytes(uuid_str: &str) -> Result<Vec<u8>, String> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Converts the CLSID constant string to a GUID
|
||||
fn parse_clsid_to_guid() -> Result<GUID, String> {
|
||||
/// Converts a CLSID string to a GUID
|
||||
pub(crate) fn parse_clsid_to_guid_str(clsid_str: &str) -> Result<GUID, String> {
|
||||
// Remove hyphens and parse as hex
|
||||
let clsid_clean = CLSID.replace("-", "");
|
||||
let clsid_clean = clsid_str.replace("-", "");
|
||||
if clsid_clean.len() != 32 {
|
||||
return Err("Invalid CLSID format".to_string());
|
||||
}
|
||||
@@ -49,6 +49,11 @@ fn parse_clsid_to_guid() -> Result<GUID, String> {
|
||||
Ok(GUID::from_u128(clsid_u128))
|
||||
}
|
||||
|
||||
/// Converts the CLSID constant string to a GUID
|
||||
fn parse_clsid_to_guid() -> Result<GUID, String> {
|
||||
parse_clsid_to_guid_str(CLSID)
|
||||
}
|
||||
|
||||
/// Generates CBOR-encoded authenticator info according to FIDO CTAP2 specifications
|
||||
/// See: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetInfo
|
||||
fn generate_cbor_authenticator_info() -> Result<Vec<u8>, String> {
|
||||
@@ -174,41 +179,35 @@ pub fn add_authenticator() -> std::result::Result<(), String> {
|
||||
let authenticator_name: HSTRING = AUTHENTICATOR_NAME.into();
|
||||
let authenticator_name_ptr = PCWSTR(authenticator_name.as_ptr()).as_ptr();
|
||||
|
||||
let clsid: HSTRING = format!("{{{}}}", CLSID).into();
|
||||
let clsid_ptr = PCWSTR(clsid.as_ptr()).as_ptr();
|
||||
// Parse CLSID into GUID structure
|
||||
let clsid_guid = parse_clsid_to_guid()
|
||||
.map_err(|e| format!("Failed to parse CLSID to GUID: {}", e))?;
|
||||
|
||||
let relying_party_id: HSTRING = RPID.into();
|
||||
let relying_party_id_ptr = PCWSTR(relying_party_id.as_ptr()).as_ptr();
|
||||
|
||||
// Generate CBOR authenticator info dynamically
|
||||
let mut authenticator_info_bytes = generate_cbor_authenticator_info()
|
||||
let authenticator_info_bytes = generate_cbor_authenticator_info()
|
||||
.map_err(|e| format!("Failed to generate authenticator info: {}", e))?;
|
||||
|
||||
let add_authenticator_options = ExperimentalWebAuthnPluginAddAuthenticatorOptions {
|
||||
let add_authenticator_options = WebAuthnPluginAddAuthenticatorOptions {
|
||||
authenticator_name: authenticator_name_ptr,
|
||||
plugin_clsid: clsid_ptr,
|
||||
rclsid: &clsid_guid, // Changed to GUID reference
|
||||
rpid: relying_party_id_ptr,
|
||||
light_theme_logo: ptr::null(),
|
||||
dark_theme_logo: ptr::null(),
|
||||
light_theme_logo_svg: ptr::null(), // Renamed field
|
||||
dark_theme_logo_svg: ptr::null(), // Renamed field
|
||||
cbor_authenticator_info_byte_count: authenticator_info_bytes.len() as u32,
|
||||
cbor_authenticator_info: authenticator_info_bytes.as_mut_ptr(),
|
||||
cbor_authenticator_info: authenticator_info_bytes.as_ptr(), // Use as_ptr() not as_mut_ptr()
|
||||
supported_rp_ids_count: 0, // NEW field: 0 means all RPs supported
|
||||
supported_rp_ids: ptr::null(), // NEW field
|
||||
};
|
||||
|
||||
let plugin_signing_public_key_byte_count: u32 = 0;
|
||||
let mut plugin_signing_public_key: c_uchar = 0;
|
||||
let plugin_signing_public_key_ptr = &mut plugin_signing_public_key;
|
||||
|
||||
let mut add_response = ExperimentalWebAuthnPluginAddAuthenticatorResponse {
|
||||
plugin_operation_signing_key_byte_count: plugin_signing_public_key_byte_count,
|
||||
plugin_operation_signing_key: plugin_signing_public_key_ptr,
|
||||
};
|
||||
let mut add_response_ptr: *mut ExperimentalWebAuthnPluginAddAuthenticatorResponse =
|
||||
&mut add_response;
|
||||
let mut add_response_ptr: *mut WebAuthnPluginAddAuthenticatorResponse = ptr::null_mut();
|
||||
|
||||
let result = unsafe {
|
||||
delay_load::<EXPERIMENTAL_WebAuthNPluginAddAuthenticatorFnDeclaration>(
|
||||
delay_load::<WebAuthNPluginAddAuthenticatorFnDeclaration>(
|
||||
s!("webauthn.dll"),
|
||||
s!("EXPERIMENTAL_WebAuthNPluginAddAuthenticator"),
|
||||
s!("WebAuthNPluginAddAuthenticator"), // Stable function name
|
||||
)
|
||||
};
|
||||
|
||||
@@ -218,24 +217,45 @@ pub fn add_authenticator() -> std::result::Result<(), String> {
|
||||
|
||||
if result.is_err() {
|
||||
return Err(format!(
|
||||
"Error: Error response from EXPERIMENTAL_WebAuthNPluginAddAuthenticator()\n{}",
|
||||
"Error: Error response from WebAuthNPluginAddAuthenticator()\n{}",
|
||||
result.message()
|
||||
));
|
||||
}
|
||||
|
||||
// Free the response if needed
|
||||
if !add_response_ptr.is_null() {
|
||||
free_add_authenticator_response(add_response_ptr);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
None => {
|
||||
Err(String::from("Error: Can't complete add_authenticator(), as the function EXPERIMENTAL_WebAuthNPluginAddAuthenticator can't be found."))
|
||||
Err(String::from("Error: Can't complete add_authenticator(), as the function WebAuthNPluginAddAuthenticator can't be found."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type EXPERIMENTAL_WebAuthNPluginAddAuthenticatorFnDeclaration = unsafe extern "cdecl" fn(
|
||||
pPluginAddAuthenticatorOptions: *const ExperimentalWebAuthnPluginAddAuthenticatorOptions,
|
||||
ppPluginAddAuthenticatorResponse: *mut *mut ExperimentalWebAuthnPluginAddAuthenticatorResponse,
|
||||
)
|
||||
-> HRESULT;
|
||||
fn free_add_authenticator_response(response: *mut WebAuthnPluginAddAuthenticatorResponse) {
|
||||
let result = unsafe {
|
||||
delay_load::<WebAuthNPluginFreeAddAuthenticatorResponseFnDeclaration>(
|
||||
s!("webauthn.dll"),
|
||||
s!("WebAuthNPluginFreeAddAuthenticatorResponse"),
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(api) = result {
|
||||
unsafe { api(response) };
|
||||
}
|
||||
}
|
||||
|
||||
type WebAuthNPluginAddAuthenticatorFnDeclaration = unsafe extern "cdecl" fn(
|
||||
pPluginAddAuthenticatorOptions: *const WebAuthnPluginAddAuthenticatorOptions,
|
||||
ppPluginAddAuthenticatorResponse: *mut *mut WebAuthnPluginAddAuthenticatorResponse,
|
||||
) -> HRESULT;
|
||||
|
||||
type WebAuthNPluginFreeAddAuthenticatorResponseFnDeclaration = unsafe extern "cdecl" fn(
|
||||
pPluginAddAuthenticatorResponse: *mut WebAuthnPluginAddAuthenticatorResponse,
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use hex;
|
||||
use serde_json;
|
||||
|
||||
use crate::com_registration::parse_clsid_to_guid_str;
|
||||
use crate::ipc::send_passkey_request;
|
||||
use crate::types::*;
|
||||
use crate::util::{debug_log, wstr_to_string};
|
||||
@@ -57,8 +58,9 @@ pub fn sync_credentials_to_windows(
|
||||
plugin_clsid
|
||||
));
|
||||
|
||||
// Format CLSID with curly braces to match Windows registration format
|
||||
let formatted_clsid = format!("{{{}}}", plugin_clsid);
|
||||
// Parse CLSID string to GUID
|
||||
let clsid_guid = parse_clsid_to_guid_str(plugin_clsid)
|
||||
.map_err(|e| format!("Failed to parse CLSID: {}", e))?;
|
||||
|
||||
if credentials.is_empty() {
|
||||
debug_log("[SYNC_TO_WIN] No credentials to sync, proceeding with empty sync");
|
||||
@@ -79,10 +81,10 @@ pub fn sync_credentials_to_windows(
|
||||
hex::encode(&cred.user_handle)
|
||||
};
|
||||
|
||||
debug_log(&format!("[SYNC_TO_WIN] Converting credential {}: RP ID: {}, User: {}, Credential ID: {} ({} bytes), User ID: {} ({} bytes)",
|
||||
debug_log(&format!("[SYNC_TO_WIN] Converting credential {}: RP ID: {}, User: {}, Credential ID: {} ({} bytes), User ID: {} ({} bytes)",
|
||||
i + 1, cred.rp_id, cred.user_name, truncated_cred_id, cred.credential_id.len(), truncated_user_id, cred.user_handle.len()));
|
||||
|
||||
let win_cred = ExperimentalWebAuthnPluginCredentialDetails::create_from_bytes(
|
||||
let win_cred = WebAuthnPluginCredentialDetails::create_from_bytes(
|
||||
cred.credential_id.clone(), // Pass raw bytes
|
||||
cred.rp_id.clone(),
|
||||
cred.rp_id.clone(), // Use RP ID as friendly name for now
|
||||
@@ -98,15 +100,9 @@ pub fn sync_credentials_to_windows(
|
||||
));
|
||||
}
|
||||
|
||||
// Create credentials list
|
||||
let credentials_list = ExperimentalWebAuthnPluginCredentialDetailsList::create(
|
||||
formatted_clsid.clone(),
|
||||
win_credentials,
|
||||
);
|
||||
|
||||
// First try to remove all existing credentials for this plugin
|
||||
debug_log("Attempting to remove all existing credentials before sync...");
|
||||
match remove_all_credentials(formatted_clsid.clone()) {
|
||||
match remove_all_credentials(clsid_guid) {
|
||||
Ok(()) => {
|
||||
debug_log("Successfully removed existing credentials");
|
||||
}
|
||||
@@ -129,7 +125,7 @@ pub fn sync_credentials_to_windows(
|
||||
Ok(())
|
||||
} else {
|
||||
debug_log("Adding new credentials to Windows...");
|
||||
match add_credentials(credentials_list) {
|
||||
match add_credentials(clsid_guid, win_credentials) {
|
||||
Ok(()) => {
|
||||
debug_log("Successfully synced credentials to Windows");
|
||||
Ok(())
|
||||
@@ -152,91 +148,36 @@ pub fn get_credentials_from_windows(plugin_clsid: &str) -> Result<Vec<SyncedCred
|
||||
plugin_clsid
|
||||
));
|
||||
|
||||
// Format CLSID with curly braces to match Windows registration format
|
||||
let formatted_clsid = format!("{{{}}}", plugin_clsid);
|
||||
// Parse CLSID string to GUID
|
||||
let clsid_guid = parse_clsid_to_guid_str(plugin_clsid)
|
||||
.map_err(|e| format!("Failed to parse CLSID: {}", e))?;
|
||||
|
||||
match get_all_credentials(formatted_clsid) {
|
||||
Ok(Some(credentials_list)) => {
|
||||
match get_all_credentials(clsid_guid) {
|
||||
Ok(credentials) => {
|
||||
debug_log(&format!(
|
||||
"Retrieved {} credentials from Windows",
|
||||
credentials_list.credential_count
|
||||
credentials.len()
|
||||
));
|
||||
|
||||
let mut bitwarden_credentials = Vec::new();
|
||||
|
||||
// Convert Windows credentials to Bitwarden format
|
||||
unsafe {
|
||||
let credentials_array = std::slice::from_raw_parts(
|
||||
credentials_list.credentials,
|
||||
credentials_list.credential_count as usize,
|
||||
);
|
||||
for cred in credentials {
|
||||
let synced_cred = SyncedCredential {
|
||||
credential_id: cred.credential_id,
|
||||
rp_id: cred.rpid,
|
||||
user_name: cred.user_name,
|
||||
user_handle: cred.user_id,
|
||||
};
|
||||
|
||||
for &cred_ptr in credentials_array {
|
||||
if !cred_ptr.is_null() {
|
||||
let cred = &*cred_ptr;
|
||||
debug_log(&format!("Converted Windows credential: RP ID: {}, User: {}, Credential ID: {} bytes",
|
||||
synced_cred.rp_id, synced_cred.user_name, synced_cred.credential_id.len()));
|
||||
|
||||
// Convert credential data back to Bitwarden format
|
||||
let credential_id = if cred.credential_id_byte_count > 0
|
||||
&& !cred.credential_id_pointer.is_null()
|
||||
{
|
||||
let id_slice = std::slice::from_raw_parts(
|
||||
cred.credential_id_pointer,
|
||||
cred.credential_id_byte_count as usize,
|
||||
);
|
||||
// Assume it's hex-encoded, try to decode
|
||||
hex::decode(std::str::from_utf8(id_slice).unwrap_or(""))
|
||||
.unwrap_or_else(|_| id_slice.to_vec())
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let rp_id = if !cred.rpid.is_null() {
|
||||
wstr_to_string(cred.rpid).unwrap_or_default()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let user_name = if !cred.user_name.is_null() {
|
||||
wstr_to_string(cred.user_name).unwrap_or_default()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let user_id =
|
||||
if cred.user_id_byte_count > 0 && !cred.user_id_pointer.is_null() {
|
||||
// Convert from UTF-8 bytes back to Vec<u8>
|
||||
let user_id_slice = std::slice::from_raw_parts(
|
||||
cred.user_id_pointer,
|
||||
cred.user_id_byte_count as usize,
|
||||
);
|
||||
// Try to decode as hex string, or use raw bytes
|
||||
let user_id_str = std::str::from_utf8(user_id_slice).unwrap_or("");
|
||||
hex::decode(user_id_str).unwrap_or_else(|_| user_id_slice.to_vec())
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let synced_cred = SyncedCredential {
|
||||
credential_id,
|
||||
rp_id,
|
||||
user_name,
|
||||
user_handle: user_id,
|
||||
};
|
||||
|
||||
debug_log(&format!("Converted Windows credential: RP ID: {}, User: {}, Credential ID: {} bytes",
|
||||
synced_cred.rp_id, synced_cred.user_name, synced_cred.credential_id.len()));
|
||||
|
||||
bitwarden_credentials.push(synced_cred);
|
||||
}
|
||||
}
|
||||
bitwarden_credentials.push(synced_cred);
|
||||
}
|
||||
|
||||
Ok(bitwarden_credentials)
|
||||
}
|
||||
Ok(None) => {
|
||||
debug_log("No credentials found in Windows");
|
||||
Ok(Vec::new())
|
||||
}
|
||||
Err(e) => {
|
||||
debug_log(&format!(
|
||||
"ERROR: Failed to get credentials from Windows: {}",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
This file exposes safe functions and types for interacting with the experimental
|
||||
Windows WebAuthn API defined here:
|
||||
This file exposes safe functions and types for interacting with the stable
|
||||
Windows WebAuthn Plugin API defined here:
|
||||
|
||||
https://github.com/microsoft/webauthn/blob/master/experimental/webauthn.h
|
||||
https://github.com/microsoft/webauthn/blob/master/webauthnplugin.h
|
||||
*/
|
||||
|
||||
use windows_core::*;
|
||||
@@ -21,49 +21,54 @@ pub struct ExperimentalWebAuthnCtapCborAuthenticatorOptions {
|
||||
pub require_resident_key: i32, // LONG lRequireResidentKey: +1=TRUE, 0=Not defined, -1=FALSE
|
||||
}
|
||||
|
||||
/// Used when adding a Windows plugin authenticator.
|
||||
/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS
|
||||
/// Header File Usage: EXPERIMENTAL_WebAuthNPluginAddAuthenticator()
|
||||
/// Used when adding a Windows plugin authenticator (stable API).
|
||||
/// Header File Name: _WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS
|
||||
/// Header File Usage: WebAuthNPluginAddAuthenticator()
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ExperimentalWebAuthnPluginAddAuthenticatorOptions {
|
||||
pub authenticator_name: *const u16,
|
||||
pub plugin_clsid: *const u16,
|
||||
pub rpid: *const u16,
|
||||
pub light_theme_logo: *const u16,
|
||||
pub dark_theme_logo: *const u16,
|
||||
pub struct WebAuthnPluginAddAuthenticatorOptions {
|
||||
pub authenticator_name: *const u16, // LPCWSTR
|
||||
pub rclsid: *const GUID, // REFCLSID (changed from string)
|
||||
pub rpid: *const u16, // LPCWSTR (optional)
|
||||
pub light_theme_logo_svg: *const u16, // LPCWSTR (optional, base64 SVG)
|
||||
pub dark_theme_logo_svg: *const u16, // LPCWSTR (optional, base64 SVG)
|
||||
pub cbor_authenticator_info_byte_count: u32,
|
||||
pub cbor_authenticator_info: *const u8,
|
||||
pub cbor_authenticator_info: *const u8, // const BYTE*
|
||||
pub supported_rp_ids_count: u32, // NEW in stable
|
||||
pub supported_rp_ids: *const *const u16, // NEW in stable: array of LPCWSTR
|
||||
}
|
||||
|
||||
/// Used as a response type when adding a Windows plugin authenticator.
|
||||
/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE
|
||||
/// Header File Usage: EXPERIMENTAL_WebAuthNPluginAddAuthenticator()
|
||||
/// EXPERIMENTAL_WebAuthNPluginFreeAddAuthenticatorResponse()
|
||||
/// Used as a response type when adding a Windows plugin authenticator (stable API).
|
||||
/// Header File Name: _WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE
|
||||
/// Header File Usage: WebAuthNPluginAddAuthenticator()
|
||||
/// WebAuthNPluginFreeAddAuthenticatorResponse()
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ExperimentalWebAuthnPluginAddAuthenticatorResponse {
|
||||
pub struct WebAuthnPluginAddAuthenticatorResponse {
|
||||
pub plugin_operation_signing_key_byte_count: u32,
|
||||
pub plugin_operation_signing_key: *mut u8,
|
||||
}
|
||||
|
||||
/// Represents a credential.
|
||||
/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS
|
||||
/// Header File Usage: _EXPERIMENTAL_WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS_LIST
|
||||
/// Header File Name: _WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS
|
||||
/// Header File Usage: WebAuthNPluginAuthenticatorAddCredentials, etc.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ExperimentalWebAuthnPluginCredentialDetails {
|
||||
pub struct WebAuthnPluginCredentialDetails {
|
||||
pub credential_id_byte_count: u32,
|
||||
pub credential_id_pointer: *mut u8,
|
||||
pub rpid: *mut u16,
|
||||
pub rp_friendly_name: *mut u16,
|
||||
pub credential_id_pointer: *const u8, // Changed to const in stable
|
||||
pub rpid: *const u16, // Changed to const (LPCWSTR)
|
||||
pub rp_friendly_name: *const u16, // Changed to const (LPCWSTR)
|
||||
pub user_id_byte_count: u32,
|
||||
pub user_id_pointer: *mut u8, // Should be *mut u8 like credential_id_pointer
|
||||
pub user_name: *mut u16,
|
||||
pub user_display_name: *mut u16,
|
||||
pub user_id_pointer: *const u8, // Changed to const
|
||||
pub user_name: *const u16, // Changed to const (LPCWSTR)
|
||||
pub user_display_name: *const u16, // Changed to const (LPCWSTR)
|
||||
}
|
||||
|
||||
impl ExperimentalWebAuthnPluginCredentialDetails {
|
||||
// Keep experimental version for internal use
|
||||
pub type ExperimentalWebAuthnPluginCredentialDetails = WebAuthnPluginCredentialDetails;
|
||||
|
||||
impl WebAuthnPluginCredentialDetails {
|
||||
pub fn create_from_bytes(
|
||||
credential_id: Vec<u8>,
|
||||
rpid: String,
|
||||
@@ -72,12 +77,11 @@ impl ExperimentalWebAuthnPluginCredentialDetails {
|
||||
user_name: String,
|
||||
user_display_name: String,
|
||||
) -> Self {
|
||||
// Convert credential_id bytes to hex string, then allocate with COM
|
||||
let (credential_id_pointer, credential_id_byte_count) = ComBuffer::from_buffer(credential_id);
|
||||
// Allocate credential_id bytes with COM
|
||||
let (credential_id_pointer, credential_id_byte_count) = ComBuffer::from_buffer(&credential_id);
|
||||
|
||||
// Convert user_id bytes to hex string, then allocate with COM
|
||||
let user_id_string = hex::encode(&user_id);
|
||||
let (user_id_pointer, user_id_byte_count) = ComBuffer::from_buffer(user_id_string.as_bytes());
|
||||
// Allocate user_id bytes with COM
|
||||
let (user_id_pointer, user_id_byte_count) = ComBuffer::from_buffer(&user_id);
|
||||
|
||||
// Convert strings to null-terminated wide strings using trait methods
|
||||
let (rpid_ptr, _) = rpid.to_com_utf16();
|
||||
@@ -87,22 +91,40 @@ impl ExperimentalWebAuthnPluginCredentialDetails {
|
||||
|
||||
Self {
|
||||
credential_id_byte_count,
|
||||
credential_id_pointer,
|
||||
rpid: rpid_ptr,
|
||||
rp_friendly_name: rp_friendly_name_ptr,
|
||||
credential_id_pointer: credential_id_pointer as *const u8,
|
||||
rpid: rpid_ptr as *const u16,
|
||||
rp_friendly_name: rp_friendly_name_ptr as *const u16,
|
||||
user_id_byte_count,
|
||||
user_id_pointer,
|
||||
user_name: user_name_ptr,
|
||||
user_display_name: user_display_name_ptr,
|
||||
user_id_pointer: user_id_pointer as *const u8,
|
||||
user_name: user_name_ptr as *const u16,
|
||||
user_display_name: user_display_name_ptr as *const u16,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a list of credentials.
|
||||
/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS_LIST
|
||||
/// Header File Usage: EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials()
|
||||
/// EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveCredentials()
|
||||
/// EXPERIMENTAL_WebAuthNPluginAuthenticatorGetAllCredentials()
|
||||
// Keep backward compat alias
|
||||
impl ExperimentalWebAuthnPluginCredentialDetails {
|
||||
pub fn create_from_bytes(
|
||||
credential_id: Vec<u8>,
|
||||
rpid: String,
|
||||
rp_friendly_name: String,
|
||||
user_id: Vec<u8>,
|
||||
user_name: String,
|
||||
user_display_name: String,
|
||||
) -> Self {
|
||||
WebAuthnPluginCredentialDetails::create_from_bytes(
|
||||
credential_id,
|
||||
rpid,
|
||||
rp_friendly_name,
|
||||
user_id,
|
||||
user_name,
|
||||
user_display_name,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a list of credentials - kept for backwards compatibility
|
||||
/// The stable API takes flat arrays directly, not this list structure
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ExperimentalWebAuthnPluginCredentialDetailsList {
|
||||
@@ -126,7 +148,7 @@ impl ExperimentalWebAuthnPluginCredentialDetailsList {
|
||||
.collect();
|
||||
|
||||
let credentials_len = credential_pointers.len();
|
||||
|
||||
|
||||
// Allocate the array of pointers using COM as well
|
||||
let credentials_pointer = if credentials_len > 0 {
|
||||
let pointer_array_bytes = credential_pointers.len() * std::mem::size_of::<*mut ExperimentalWebAuthnPluginCredentialDetails>();
|
||||
@@ -143,7 +165,7 @@ impl ExperimentalWebAuthnPluginCredentialDetailsList {
|
||||
|
||||
// Convert CLSID to wide string using trait method
|
||||
let (clsid_ptr, _) = clsid.to_com_utf16();
|
||||
|
||||
|
||||
Self {
|
||||
plugin_clsid: clsid_ptr,
|
||||
credential_count: credentials_len as u32,
|
||||
@@ -152,52 +174,71 @@ impl ExperimentalWebAuthnPluginCredentialDetailsList {
|
||||
}
|
||||
}
|
||||
|
||||
pub type EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentialsFnDeclaration =
|
||||
// Stable API function signatures - now use REFCLSID and flat arrays
|
||||
pub type WebAuthNPluginAuthenticatorAddCredentialsFnDeclaration =
|
||||
unsafe extern "cdecl" fn(
|
||||
pCredentialDetailsList: *mut ExperimentalWebAuthnPluginCredentialDetailsList,
|
||||
rclsid: *const GUID, // Changed from string to GUID reference
|
||||
cCredentialDetails: u32,
|
||||
pCredentialDetails: *const WebAuthnPluginCredentialDetails, // Flat array, not list
|
||||
) -> HRESULT;
|
||||
|
||||
pub type EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveCredentialsFnDeclaration =
|
||||
pub type WebAuthNPluginAuthenticatorRemoveCredentialsFnDeclaration =
|
||||
unsafe extern "cdecl" fn(
|
||||
pCredentialDetailsList: *mut ExperimentalWebAuthnPluginCredentialDetailsList,
|
||||
rclsid: *const GUID,
|
||||
cCredentialDetails: u32,
|
||||
pCredentialDetails: *const WebAuthnPluginCredentialDetails,
|
||||
) -> HRESULT;
|
||||
|
||||
pub type EXPERIMENTAL_WebAuthNPluginAuthenticatorGetAllCredentialsFnDeclaration =
|
||||
pub type WebAuthNPluginAuthenticatorGetAllCredentialsFnDeclaration =
|
||||
unsafe extern "cdecl" fn(
|
||||
pwszPluginClsId: *const u16,
|
||||
ppCredentialDetailsList: *mut *mut ExperimentalWebAuthnPluginCredentialDetailsList,
|
||||
rclsid: *const GUID,
|
||||
pcCredentialDetails: *mut u32, // Out param for count
|
||||
ppCredentialDetailsArray: *mut *mut WebAuthnPluginCredentialDetails, // Out param for array
|
||||
) -> HRESULT;
|
||||
|
||||
pub type EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveAllCredentialsFnDeclaration =
|
||||
pub type WebAuthNPluginAuthenticatorFreeCredentialDetailsArrayFnDeclaration =
|
||||
unsafe extern "cdecl" fn(
|
||||
pwszPluginClsId: *const u16,
|
||||
cCredentialDetails: u32,
|
||||
pCredentialDetailsArray: *mut WebAuthnPluginCredentialDetails,
|
||||
);
|
||||
|
||||
pub type WebAuthNPluginAuthenticatorRemoveAllCredentialsFnDeclaration =
|
||||
unsafe extern "cdecl" fn(
|
||||
rclsid: *const GUID,
|
||||
) -> HRESULT;
|
||||
|
||||
pub fn add_credentials(
|
||||
mut credentials_list: ExperimentalWebAuthnPluginCredentialDetailsList,
|
||||
clsid_guid: GUID,
|
||||
credentials: Vec<WebAuthnPluginCredentialDetails>,
|
||||
) -> std::result::Result<(), String> {
|
||||
debug_log("Loading EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials function...");
|
||||
|
||||
debug_log("Loading WebAuthNPluginAuthenticatorAddCredentials function...");
|
||||
|
||||
let result = unsafe {
|
||||
delay_load::<EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentialsFnDeclaration>(
|
||||
delay_load::<WebAuthNPluginAuthenticatorAddCredentialsFnDeclaration>(
|
||||
s!("webauthn.dll"),
|
||||
s!("EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials"),
|
||||
s!("WebAuthNPluginAuthenticatorAddCredentials"),
|
||||
)
|
||||
};
|
||||
|
||||
match result {
|
||||
Some(api) => {
|
||||
debug_log("Function loaded successfully, calling API...");
|
||||
debug_log(&format!("Credential list: plugin_clsid valid: {}, credential_count: {}",
|
||||
!credentials_list.plugin_clsid.is_null(), credentials_list.credential_count));
|
||||
|
||||
let result = unsafe { api(&mut credentials_list) };
|
||||
debug_log(&format!("Adding {} credentials", credentials.len()));
|
||||
|
||||
let credential_count = credentials.len() as u32;
|
||||
let credentials_ptr = if credentials.is_empty() {
|
||||
std::ptr::null()
|
||||
} else {
|
||||
credentials.as_ptr()
|
||||
};
|
||||
|
||||
let result = unsafe { api(&clsid_guid, credential_count, credentials_ptr) };
|
||||
|
||||
if result.is_err() {
|
||||
let error_code = result.0;
|
||||
debug_log(&format!("API call failed with HRESULT: 0x{:x}", error_code));
|
||||
return Err(format!(
|
||||
"Error: Error response from EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials()\nHRESULT: 0x{:x}\n{}",
|
||||
"Error: Error response from WebAuthNPluginAuthenticatorAddCredentials()\nHRESULT: 0x{:x}\n{}",
|
||||
error_code, result.message()
|
||||
));
|
||||
}
|
||||
@@ -206,29 +247,41 @@ pub fn add_credentials(
|
||||
Ok(())
|
||||
},
|
||||
None => {
|
||||
debug_log("Failed to load EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials function from webauthn.dll");
|
||||
Err(String::from("Error: Can't complete add_credentials(), as the function EXPERIMENTAL_WebAuthNPluginAuthenticatorAddCredentials can't be loaded."))
|
||||
debug_log("Failed to load WebAuthNPluginAuthenticatorAddCredentials function from webauthn.dll");
|
||||
Err(String::from("Error: Can't complete add_credentials(), as the function WebAuthNPluginAuthenticatorAddCredentials can't be loaded."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_credentials(
|
||||
mut credentials_list: ExperimentalWebAuthnPluginCredentialDetailsList,
|
||||
clsid_guid: GUID,
|
||||
credentials: Vec<WebAuthnPluginCredentialDetails>,
|
||||
) -> std::result::Result<(), String> {
|
||||
debug_log("Loading WebAuthNPluginAuthenticatorRemoveCredentials function...");
|
||||
|
||||
let result = unsafe {
|
||||
delay_load::<EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveCredentialsFnDeclaration>(
|
||||
delay_load::<WebAuthNPluginAuthenticatorRemoveCredentialsFnDeclaration>(
|
||||
s!("webauthn.dll"),
|
||||
s!("EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveCredentials"),
|
||||
s!("WebAuthNPluginAuthenticatorRemoveCredentials"),
|
||||
)
|
||||
};
|
||||
|
||||
match result {
|
||||
Some(api) => {
|
||||
let result = unsafe { api(&mut credentials_list) };
|
||||
debug_log(&format!("Removing {} credentials", credentials.len()));
|
||||
|
||||
let credential_count = credentials.len() as u32;
|
||||
let credentials_ptr = if credentials.is_empty() {
|
||||
std::ptr::null()
|
||||
} else {
|
||||
credentials.as_ptr()
|
||||
};
|
||||
|
||||
let result = unsafe { api(&clsid_guid, credential_count, credentials_ptr) };
|
||||
|
||||
if result.is_err() {
|
||||
return Err(format!(
|
||||
"Error: Error response from EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveCredentials()\n{}",
|
||||
"Error: Error response from WebAuthNPluginAuthenticatorRemoveCredentials()\n{}",
|
||||
result.message()
|
||||
));
|
||||
}
|
||||
@@ -236,75 +289,181 @@ pub fn remove_credentials(
|
||||
Ok(())
|
||||
},
|
||||
None => {
|
||||
Err(String::from("Error: Can't complete remove_credentials(), as the function EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveCredentials can't be loaded."))
|
||||
Err(String::from("Error: Can't complete remove_credentials(), as the function WebAuthNPluginAuthenticatorRemoveCredentials can't be loaded."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper struct to hold owned credential data
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OwnedCredentialDetails {
|
||||
pub credential_id: Vec<u8>,
|
||||
pub rpid: String,
|
||||
pub rp_friendly_name: String,
|
||||
pub user_id: Vec<u8>,
|
||||
pub user_name: String,
|
||||
pub user_display_name: String,
|
||||
}
|
||||
|
||||
pub fn get_all_credentials(
|
||||
plugin_clsid: String,
|
||||
) -> std::result::Result<Option<ExperimentalWebAuthnPluginCredentialDetailsList>, String> {
|
||||
clsid_guid: GUID,
|
||||
) -> std::result::Result<Vec<OwnedCredentialDetails>, String> {
|
||||
debug_log("Loading WebAuthNPluginAuthenticatorGetAllCredentials function...");
|
||||
|
||||
let result = unsafe {
|
||||
delay_load::<EXPERIMENTAL_WebAuthNPluginAuthenticatorGetAllCredentialsFnDeclaration>(
|
||||
delay_load::<WebAuthNPluginAuthenticatorGetAllCredentialsFnDeclaration>(
|
||||
s!("webauthn.dll"),
|
||||
s!("EXPERIMENTAL_WebAuthNPluginAuthenticatorGetAllCredentials"),
|
||||
s!("WebAuthNPluginAuthenticatorGetAllCredentials"),
|
||||
)
|
||||
};
|
||||
|
||||
match result {
|
||||
Some(api) => {
|
||||
// Create the wide string and keep it alive during the API call
|
||||
let clsid_wide = plugin_clsid.to_utf16();
|
||||
let mut credentials_list_ptr: *mut ExperimentalWebAuthnPluginCredentialDetailsList = std::ptr::null_mut();
|
||||
|
||||
let result = unsafe { api(clsid_wide.as_ptr(), &mut credentials_list_ptr) };
|
||||
let mut credential_count: u32 = 0;
|
||||
let mut credentials_array_ptr: *mut WebAuthnPluginCredentialDetails = std::ptr::null_mut();
|
||||
|
||||
let result = unsafe { api(&clsid_guid, &mut credential_count, &mut credentials_array_ptr) };
|
||||
|
||||
if result.is_err() {
|
||||
return Err(format!(
|
||||
"Error: Error response from EXPERIMENTAL_WebAuthNPluginAuthenticatorGetAllCredentials()\n{}",
|
||||
"Error: Error response from WebAuthNPluginAuthenticatorGetAllCredentials()\n{}",
|
||||
result.message()
|
||||
));
|
||||
}
|
||||
|
||||
if credentials_list_ptr.is_null() {
|
||||
Ok(None)
|
||||
} else {
|
||||
// Note: The caller is responsible for managing the memory of the returned list
|
||||
Ok(Some(unsafe { *credentials_list_ptr }))
|
||||
if credentials_array_ptr.is_null() || credential_count == 0 {
|
||||
debug_log("No credentials returned");
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
// Deep copy the credential data before Windows frees it
|
||||
let credentials_slice = unsafe {
|
||||
std::slice::from_raw_parts(credentials_array_ptr, credential_count as usize)
|
||||
};
|
||||
|
||||
let mut owned_credentials = Vec::new();
|
||||
for cred in credentials_slice {
|
||||
unsafe {
|
||||
// Copy credential ID bytes
|
||||
let credential_id = if !cred.credential_id_pointer.is_null() && cred.credential_id_byte_count > 0 {
|
||||
std::slice::from_raw_parts(cred.credential_id_pointer, cred.credential_id_byte_count as usize).to_vec()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
// Copy user ID bytes
|
||||
let user_id = if !cred.user_id_pointer.is_null() && cred.user_id_byte_count > 0 {
|
||||
std::slice::from_raw_parts(cred.user_id_pointer, cred.user_id_byte_count as usize).to_vec()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
// Copy string fields
|
||||
let rpid = if !cred.rpid.is_null() {
|
||||
String::from_utf16_lossy(std::slice::from_raw_parts(
|
||||
cred.rpid,
|
||||
(0..).position(|i| *cred.rpid.offset(i) == 0).unwrap_or(0)
|
||||
))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let rp_friendly_name = if !cred.rp_friendly_name.is_null() {
|
||||
String::from_utf16_lossy(std::slice::from_raw_parts(
|
||||
cred.rp_friendly_name,
|
||||
(0..).position(|i| *cred.rp_friendly_name.offset(i) == 0).unwrap_or(0)
|
||||
))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let user_name = if !cred.user_name.is_null() {
|
||||
String::from_utf16_lossy(std::slice::from_raw_parts(
|
||||
cred.user_name,
|
||||
(0..).position(|i| *cred.user_name.offset(i) == 0).unwrap_or(0)
|
||||
))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let user_display_name = if !cred.user_display_name.is_null() {
|
||||
String::from_utf16_lossy(std::slice::from_raw_parts(
|
||||
cred.user_display_name,
|
||||
(0..).position(|i| *cred.user_display_name.offset(i) == 0).unwrap_or(0)
|
||||
))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
owned_credentials.push(OwnedCredentialDetails {
|
||||
credential_id,
|
||||
rpid,
|
||||
rp_friendly_name,
|
||||
user_id,
|
||||
user_name,
|
||||
user_display_name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Free the array using the Windows API - this frees everything including strings
|
||||
free_credential_details_array(credential_count, credentials_array_ptr);
|
||||
|
||||
debug_log(&format!("Retrieved {} credentials", owned_credentials.len()));
|
||||
Ok(owned_credentials)
|
||||
},
|
||||
None => {
|
||||
Err(String::from("Error: Can't complete get_all_credentials(), as the function EXPERIMENTAL_WebAuthNPluginAuthenticatorGetAllCredentials can't be loaded."))
|
||||
Err(String::from("Error: Can't complete get_all_credentials(), as the function WebAuthNPluginAuthenticatorGetAllCredentials can't be loaded."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_all_credentials(
|
||||
plugin_clsid: String,
|
||||
) -> std::result::Result<(), String> {
|
||||
debug_log("Loading EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveAllCredentials function...");
|
||||
|
||||
fn free_credential_details_array(
|
||||
credential_count: u32,
|
||||
credentials_array: *mut WebAuthnPluginCredentialDetails,
|
||||
) {
|
||||
if credentials_array.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let result = unsafe {
|
||||
delay_load::<EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveAllCredentialsFnDeclaration>(
|
||||
delay_load::<WebAuthNPluginAuthenticatorFreeCredentialDetailsArrayFnDeclaration>(
|
||||
s!("webauthn.dll"),
|
||||
s!("EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveAllCredentials"),
|
||||
s!("WebAuthNPluginAuthenticatorFreeCredentialDetailsArray"),
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(api) = result {
|
||||
unsafe { api(credential_count, credentials_array) };
|
||||
} else {
|
||||
debug_log("Warning: Could not load WebAuthNPluginAuthenticatorFreeCredentialDetailsArray");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_all_credentials(
|
||||
clsid_guid: GUID,
|
||||
) -> std::result::Result<(), String> {
|
||||
debug_log("Loading WebAuthNPluginAuthenticatorRemoveAllCredentials function...");
|
||||
|
||||
let result = unsafe {
|
||||
delay_load::<WebAuthNPluginAuthenticatorRemoveAllCredentialsFnDeclaration>(
|
||||
s!("webauthn.dll"),
|
||||
s!("WebAuthNPluginAuthenticatorRemoveAllCredentials"),
|
||||
)
|
||||
};
|
||||
|
||||
match result {
|
||||
Some(api) => {
|
||||
debug_log("Function loaded successfully, calling API...");
|
||||
// Create the wide string and keep it alive during the API call
|
||||
let clsid_wide = plugin_clsid.to_utf16();
|
||||
|
||||
let result = unsafe { api(clsid_wide.as_ptr()) };
|
||||
|
||||
let result = unsafe { api(&clsid_guid) };
|
||||
|
||||
if result.is_err() {
|
||||
let error_code = result.0;
|
||||
debug_log(&format!("API call failed with HRESULT: 0x{:x}", error_code));
|
||||
|
||||
|
||||
return Err(format!(
|
||||
"Error: Error response from EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveAllCredentials()\nHRESULT: 0x{:x}\n{}",
|
||||
"Error: Error response from WebAuthNPluginAuthenticatorRemoveAllCredentials()\nHRESULT: 0x{:x}\n{}",
|
||||
error_code, result.message()
|
||||
));
|
||||
}
|
||||
@@ -313,8 +472,8 @@ pub fn remove_all_credentials(
|
||||
Ok(())
|
||||
},
|
||||
None => {
|
||||
debug_log("Failed to load EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveAllCredentials function from webauthn.dll");
|
||||
Err(String::from("Error: Can't complete remove_all_credentials(), as the function EXPERIMENTAL_WebAuthNPluginAuthenticatorRemoveAllCredentials can't be loaded."))
|
||||
debug_log("Failed to load WebAuthNPluginAuthenticatorRemoveAllCredentials function from webauthn.dll");
|
||||
Err(String::from("Error: Can't complete remove_all_credentials(), as the function WebAuthNPluginAuthenticatorRemoveAllCredentials can't be loaded."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user