mirror of
https://github.com/bitwarden/browser
synced 2026-02-14 15:33:55 +00:00
Cleanup
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
pub(crate) mod com;
|
||||
pub(crate) mod types;
|
||||
|
||||
use std::{error::Error, mem::MaybeUninit, ptr::NonNull};
|
||||
use std::{error::Error, ptr::NonNull};
|
||||
use types::*;
|
||||
use windows::{
|
||||
core::GUID,
|
||||
|
||||
@@ -34,7 +34,7 @@ use super::Clsid;
|
||||
/// Header File Name: _WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct WebAuthnCtapCborAuthenticatorOptions {
|
||||
pub struct WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS {
|
||||
dwVersion: u32,
|
||||
// LONG lUp: +1=TRUE, 0=Not defined, -1=FALSE
|
||||
lUp: i32,
|
||||
@@ -69,7 +69,8 @@ impl WebAuthnCtapCborAuthenticatorOptions {
|
||||
}
|
||||
}
|
||||
}
|
||||
type WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS = WebAuthnCtapCborAuthenticatorOptions;
|
||||
|
||||
pub type WebAuthnCtapCborAuthenticatorOptions = WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS;
|
||||
|
||||
/// Used when adding a Windows plugin authenticator (stable API).
|
||||
/// Header File Name: _WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS
|
||||
@@ -369,7 +370,7 @@ pub enum WebAuthnPluginRequestType {
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST {
|
||||
pub(super) struct WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST {
|
||||
pub dwVersion: u32,
|
||||
pub cbRpId: u32,
|
||||
pub pbRpId: *const u8,
|
||||
@@ -755,7 +756,7 @@ webauthn_call!("WebAuthNEncodeMakeCredentialResponse" as fn webauthn_encode_make
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST {
|
||||
pub(super) struct WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST {
|
||||
pub dwVersion: u32,
|
||||
pub pwszRpId: *const u16, // PCWSTR
|
||||
pub cbRpId: u32,
|
||||
|
||||
@@ -8,12 +8,7 @@ use std::{collections::HashSet, fmt::Display, ptr::NonNull};
|
||||
use ciborium::Value;
|
||||
use windows_core::PCWSTR;
|
||||
|
||||
use crate::{
|
||||
// com::ComBuffer,
|
||||
util::ArrayPointerIterator,
|
||||
ErrorKind,
|
||||
WinWebAuthnError,
|
||||
};
|
||||
use crate::{util::ArrayPointerIterator, ErrorKind, WinWebAuthnError};
|
||||
|
||||
/// List of its supported protocol versions and extensions, its AAGUID, and
|
||||
/// other aspects of its overall capabilities.
|
||||
@@ -629,36 +624,6 @@ pub struct CredentialEx {
|
||||
inner: NonNull<WEBAUTHN_CREDENTIAL_EX>,
|
||||
}
|
||||
|
||||
impl CredentialEx {
|
||||
/*
|
||||
fn new_for_com(
|
||||
version: u32,
|
||||
id: &CredentialId,
|
||||
credential_type: &str,
|
||||
transports: &[CtapTransport],
|
||||
) -> Self {
|
||||
let (pwszCredentialType, _) = credential_type.to_com_utf16();
|
||||
let (pbId, cbId) = ComBuffer::from_buffer(&id);
|
||||
let ptr = unsafe {
|
||||
let mut uninit: MaybeUninit<WEBAUTHN_CREDENTIAL_EX> = MaybeUninit::uninit();
|
||||
let ptr = uninit.as_mut_ptr();
|
||||
std::ptr::write(
|
||||
ptr,
|
||||
WEBAUTHN_CREDENTIAL_EX {
|
||||
dwVersion: version,
|
||||
cbId,
|
||||
pbId,
|
||||
pwszCredentialType,
|
||||
dwTransports: transports.iter().map(|t| t.clone() as u32).sum(),
|
||||
},
|
||||
);
|
||||
NonNull::new_unchecked(ptr)
|
||||
};
|
||||
Self { inner: ptr }
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
impl AsRef<WEBAUTHN_CREDENTIAL_EX> for CredentialEx {
|
||||
fn as_ref(&self) -> &WEBAUTHN_CREDENTIAL_EX {
|
||||
// SAFETY: We initialize memory manually in constructors.
|
||||
|
||||
@@ -6,11 +6,7 @@ use windows::{
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
// com::ComBuffer,
|
||||
ErrorKind,
|
||||
WinWebAuthnError,
|
||||
};
|
||||
use crate::{ErrorKind, WinWebAuthnError};
|
||||
|
||||
macro_rules! webauthn_call {
|
||||
($symbol:literal as fn $fn_name:ident($($arg:ident: $arg_type:ty),+) -> $result_type:ty) => (
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
use std::alloc;
|
||||
use std::mem::{align_of, MaybeUninit};
|
||||
use std::ptr::NonNull;
|
||||
use windows::Win32::System::Com::CoTaskMemAlloc;
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct ComBuffer(NonNull<MaybeUninit<u8>>);
|
||||
|
||||
impl ComBuffer {
|
||||
/// Returns an COM-allocated buffer of `size`.
|
||||
fn alloc(size: usize, for_slice: bool) -> Self {
|
||||
#[expect(clippy::as_conversions)]
|
||||
{
|
||||
assert!(size <= isize::MAX as usize, "requested bad object size");
|
||||
}
|
||||
|
||||
// SAFETY: Any size is valid to pass to Windows, even `0`.
|
||||
let ptr = NonNull::new(unsafe { CoTaskMemAlloc(size) }).unwrap_or_else(|| {
|
||||
// XXX: This doesn't have to be correct, just close enough for an OK OOM error.
|
||||
let layout = alloc::Layout::from_size_align(size, align_of::<u8>()).unwrap();
|
||||
alloc::handle_alloc_error(layout)
|
||||
});
|
||||
|
||||
if for_slice {
|
||||
// Ininitialize the buffer so it can later be treated as `&mut [u8]`.
|
||||
// SAFETY: The pointer is valid and we are using a valid value for a byte-wise allocation.
|
||||
unsafe { ptr.write_bytes(0, size) };
|
||||
}
|
||||
|
||||
Self(ptr.cast())
|
||||
}
|
||||
|
||||
fn into_ptr<T>(self) -> *mut T {
|
||||
self.0.cast().as_ptr()
|
||||
}
|
||||
|
||||
/// Creates a new COM-allocated structure.
|
||||
///
|
||||
/// Note that `T` must be [Copy] to avoid any possible memory leaks.
|
||||
pub fn with_object<T: Copy>(object: T) -> *mut T {
|
||||
// NB: Vendored from Rust's alloc code since we can't yet allocate `Box` with a custom allocator.
|
||||
const MIN_ALIGN: usize = if cfg!(target_pointer_width = "64") {
|
||||
16
|
||||
} else if cfg!(target_pointer_width = "32") {
|
||||
8
|
||||
} else {
|
||||
panic!("unsupported arch")
|
||||
};
|
||||
|
||||
// SAFETY: Validate that our alignment works for a normal size-based allocation for soundness.
|
||||
let layout = const {
|
||||
let layout = alloc::Layout::new::<T>();
|
||||
assert!(layout.align() <= MIN_ALIGN);
|
||||
layout
|
||||
};
|
||||
|
||||
let buffer = Self::alloc(layout.size(), false);
|
||||
// SAFETY: `ptr` is valid for writes of `T` because we correctly allocated the right sized buffer that
|
||||
// accounts for any alignment requirements.
|
||||
//
|
||||
// Additionally, we ensure the value is treated as moved by forgetting the source.
|
||||
unsafe { buffer.0.cast::<T>().write(object) };
|
||||
buffer.into_ptr()
|
||||
}
|
||||
|
||||
pub fn from_buffer<T: AsRef<[u8]>>(buffer: T) -> (*mut u8, u32) {
|
||||
let buffer = buffer.as_ref();
|
||||
let len = buffer.len();
|
||||
let com_buffer = Self::alloc(len, true);
|
||||
|
||||
// SAFETY: `ptr` points to a valid allocation that `len` matches, and we made sure
|
||||
// the bytes were initialized. Additionally, bytes have no alignment requirements.
|
||||
unsafe {
|
||||
NonNull::slice_from_raw_parts(com_buffer.0.cast::<u8>(), len)
|
||||
.as_mut()
|
||||
.copy_from_slice(buffer)
|
||||
}
|
||||
|
||||
// Safety: The Windows API structures these buffers are placed into use `u32` (`DWORD`) to
|
||||
// represent length.
|
||||
#[expect(clippy::as_conversions)]
|
||||
(com_buffer.into_ptr(), len as u32)
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,10 @@
|
||||
|
||||
// New modular structure
|
||||
mod assert;
|
||||
mod com_buffer;
|
||||
// mod com_provider;
|
||||
mod ipc2;
|
||||
mod make_credential;
|
||||
mod types;
|
||||
mod util;
|
||||
mod webauthn;
|
||||
|
||||
use std::{collections::HashSet, sync::Arc, time::Duration};
|
||||
|
||||
@@ -25,10 +22,7 @@ use win_webauthn::{
|
||||
AuthenticatorInfo, CtapVersion, PublicKeyCredentialParameters,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ipc2::{ConnectionStatus, TimedCallback, WindowsProviderClient},
|
||||
make_credential::make_credential,
|
||||
};
|
||||
use crate::ipc2::{TimedCallback, WindowsProviderClient};
|
||||
|
||||
const AUTHENTICATOR_NAME: &str = "Bitwarden Desktop";
|
||||
const RPID: &str = "bitwarden.com";
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
use std::fs::{create_dir_all, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use windows::Win32::UI::HiDpi::GetDpiForWindow;
|
||||
use windows::{
|
||||
core::PCSTR,
|
||||
Win32::{Foundation::*, System::LibraryLoader::*, UI::WindowsAndMessaging::GetWindowRect},
|
||||
};
|
||||
|
||||
use crate::com_buffer::ComBuffer;
|
||||
use windows::Win32::{Foundation::*, UI::WindowsAndMessaging::GetWindowRect};
|
||||
|
||||
const BASE_DPI: u32 = 96;
|
||||
|
||||
@@ -44,90 +34,3 @@ impl HwndExt for HWND {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn delay_load<T>(library: PCSTR, function: PCSTR) -> Option<T> {
|
||||
let library = LoadLibraryExA(library, None, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
|
||||
|
||||
let Ok(library) = library else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let address = GetProcAddress(library, function);
|
||||
|
||||
if address.is_some() {
|
||||
return Some(std::mem::transmute_copy(&address));
|
||||
}
|
||||
|
||||
_ = FreeLibrary(library);
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Trait for converting strings to Windows-compatible wide strings using COM allocation
|
||||
pub trait WindowsString {
|
||||
/// Converts to null-terminated UTF-16 using COM allocation
|
||||
fn to_com_utf16(&self) -> (*mut u16, u32);
|
||||
/// Converts to Vec<u16> for temporary use (caller must keep Vec alive)
|
||||
fn to_utf16(&self) -> Vec<u16>;
|
||||
}
|
||||
|
||||
impl WindowsString for str {
|
||||
fn to_com_utf16(&self) -> (*mut u16, u32) {
|
||||
let mut wide_vec: Vec<u16> = self.encode_utf16().collect();
|
||||
wide_vec.push(0); // null terminator
|
||||
let wide_bytes: Vec<u8> = wide_vec.iter().flat_map(|&x| x.to_le_bytes()).collect();
|
||||
let (ptr, byte_count) = ComBuffer::from_buffer(&wide_bytes);
|
||||
(ptr as *mut u16, byte_count)
|
||||
}
|
||||
|
||||
fn to_utf16(&self) -> Vec<u16> {
|
||||
let mut wide_vec: Vec<u16> = self.encode_utf16().collect();
|
||||
wide_vec.push(0); // null terminator
|
||||
wide_vec
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_log(msg: &str) {
|
||||
let log_path = "C:\\temp\\bitwarden_com_debug.log";
|
||||
|
||||
// Create the temp directory if it doesn't exist
|
||||
if let Some(parent) = Path::new(log_path).parent() {
|
||||
let _ = create_dir_all(parent);
|
||||
}
|
||||
|
||||
if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(log_path) {
|
||||
let now = SystemTime::now();
|
||||
let timestamp = match now.duration_since(UNIX_EPOCH) {
|
||||
Ok(duration) => {
|
||||
let total_secs = duration.as_secs();
|
||||
let millis = duration.subsec_millis();
|
||||
let secs = total_secs % 60;
|
||||
let mins = (total_secs / 60) % 60;
|
||||
let hours = (total_secs / 3600) % 24;
|
||||
format!("{:02}:{:02}:{:02}.{:03}", hours, mins, secs, millis)
|
||||
}
|
||||
Err(_) => "??:??:??.???".to_string(),
|
||||
};
|
||||
|
||||
let _ = writeln!(file, "[{}] {}", timestamp, msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to convert Windows wide string (UTF-16) to Rust String
|
||||
pub unsafe fn wstr_to_string(
|
||||
wstr_ptr: *const u16,
|
||||
) -> std::result::Result<String, std::string::FromUtf16Error> {
|
||||
if wstr_ptr.is_null() {
|
||||
return Ok(String::new());
|
||||
}
|
||||
|
||||
// Find the length of the null-terminated wide string
|
||||
let mut len = 0;
|
||||
while *wstr_ptr.add(len) != 0 {
|
||||
len += 1;
|
||||
}
|
||||
|
||||
// Convert to Rust string
|
||||
let wide_slice = std::slice::from_raw_parts(wstr_ptr, len);
|
||||
String::from_utf16(wide_slice)
|
||||
}
|
||||
|
||||
@@ -1,420 +0,0 @@
|
||||
/*
|
||||
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/webauthnplugin.h
|
||||
*/
|
||||
|
||||
use windows::core::*;
|
||||
|
||||
use crate::com_buffer::ComBuffer;
|
||||
use crate::util::{delay_load, WindowsString};
|
||||
|
||||
/// Windows WebAuthn Authenticator Options structure
|
||||
/// Header File Name: _WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct WebAuthnCtapCborAuthenticatorOptions {
|
||||
pub version: u32, // DWORD dwVersion
|
||||
pub user_presence: i32, // LONG lUp: +1=TRUE, 0=Not defined, -1=FALSE
|
||||
pub user_verification: i32, // LONG lUv: +1=TRUE, 0=Not defined, -1=FALSE
|
||||
pub require_resident_key: i32, // LONG lRequireResidentKey: +1=TRUE, 0=Not defined, -1=FALSE
|
||||
}
|
||||
|
||||
/// 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 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, // 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 (stable API).
|
||||
/// Header File Name: _WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE
|
||||
/// Header File Usage: WebAuthNPluginAddAuthenticator()
|
||||
/// WebAuthNPluginFreeAddAuthenticatorResponse()
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct WebAuthnPluginAddAuthenticatorResponse {
|
||||
pub plugin_operation_signing_key_byte_count: u32,
|
||||
pub plugin_operation_signing_key: *mut u8,
|
||||
}
|
||||
|
||||
/// Represents a credential.
|
||||
/// Header File Name: _WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS
|
||||
/// Header File Usage: WebAuthNPluginAuthenticatorAddCredentials, etc.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct WebAuthnPluginCredentialDetails {
|
||||
pub credential_id_byte_count: u32,
|
||||
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: *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 WebAuthnPluginCredentialDetails {
|
||||
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 {
|
||||
// Allocate credential_id bytes with COM
|
||||
let (credential_id_pointer, credential_id_byte_count) =
|
||||
ComBuffer::from_buffer(&credential_id);
|
||||
|
||||
// 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();
|
||||
let (rp_friendly_name_ptr, _) = rp_friendly_name.to_com_utf16();
|
||||
let (user_name_ptr, _) = user_name.to_com_utf16();
|
||||
let (user_display_name_ptr, _) = user_display_name.to_com_utf16();
|
||||
|
||||
Self {
|
||||
credential_id_byte_count,
|
||||
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_id_pointer as *const u8,
|
||||
user_name: user_name_ptr as *const u16,
|
||||
user_display_name: user_display_name_ptr as *const u16,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stable API function signatures - now use REFCLSID and flat arrays
|
||||
pub type WebAuthNPluginAuthenticatorAddCredentialsFnDeclaration =
|
||||
unsafe extern "cdecl" fn(
|
||||
rclsid: *const GUID, // Changed from string to GUID reference
|
||||
cCredentialDetails: u32,
|
||||
pCredentialDetails: *const WebAuthnPluginCredentialDetails, // Flat array, not list
|
||||
) -> HRESULT;
|
||||
|
||||
pub type WebAuthNPluginAuthenticatorRemoveCredentialsFnDeclaration =
|
||||
unsafe extern "cdecl" fn(
|
||||
rclsid: *const GUID,
|
||||
cCredentialDetails: u32,
|
||||
pCredentialDetails: *const WebAuthnPluginCredentialDetails,
|
||||
) -> HRESULT;
|
||||
|
||||
pub type WebAuthNPluginAuthenticatorGetAllCredentialsFnDeclaration =
|
||||
unsafe extern "cdecl" fn(
|
||||
rclsid: *const GUID,
|
||||
pcCredentialDetails: *mut u32, // Out param for count
|
||||
ppCredentialDetailsArray: *mut *mut WebAuthnPluginCredentialDetails, // Out param for array
|
||||
) -> HRESULT;
|
||||
|
||||
pub type WebAuthNPluginAuthenticatorFreeCredentialDetailsArrayFnDeclaration =
|
||||
unsafe extern "cdecl" fn(
|
||||
cCredentialDetails: u32,
|
||||
pCredentialDetailsArray: *mut WebAuthnPluginCredentialDetails,
|
||||
);
|
||||
|
||||
pub type WebAuthNPluginAuthenticatorRemoveAllCredentialsFnDeclaration =
|
||||
unsafe extern "cdecl" fn(rclsid: *const GUID) -> HRESULT;
|
||||
|
||||
pub fn add_credentials(
|
||||
clsid_guid: GUID,
|
||||
credentials: Vec<WebAuthnPluginCredentialDetails>,
|
||||
) -> std::result::Result<(), String> {
|
||||
tracing::debug!("Loading WebAuthNPluginAuthenticatorAddCredentials function...");
|
||||
|
||||
let result = unsafe {
|
||||
delay_load::<WebAuthNPluginAuthenticatorAddCredentialsFnDeclaration>(
|
||||
s!("webauthn.dll"),
|
||||
s!("WebAuthNPluginAuthenticatorAddCredentials"),
|
||||
)
|
||||
};
|
||||
|
||||
match result {
|
||||
Some(api) => {
|
||||
tracing::debug!("Function loaded successfully, calling API...");
|
||||
tracing::debug!("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;
|
||||
tracing::debug!("API call failed with HRESULT: 0x{:x}", error_code);
|
||||
return Err(format!(
|
||||
"Error: Error response from WebAuthNPluginAuthenticatorAddCredentials()\nHRESULT: 0x{:x}\n{}",
|
||||
error_code, result.message()
|
||||
));
|
||||
}
|
||||
|
||||
tracing::debug!("API call succeeded");
|
||||
Ok(())
|
||||
}
|
||||
None => {
|
||||
tracing::debug!("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(
|
||||
clsid_guid: GUID,
|
||||
credentials: Vec<WebAuthnPluginCredentialDetails>,
|
||||
) -> std::result::Result<(), String> {
|
||||
tracing::debug!("Loading WebAuthNPluginAuthenticatorRemoveCredentials function...");
|
||||
|
||||
let result = unsafe {
|
||||
delay_load::<WebAuthNPluginAuthenticatorRemoveCredentialsFnDeclaration>(
|
||||
s!("webauthn.dll"),
|
||||
s!("WebAuthNPluginAuthenticatorRemoveCredentials"),
|
||||
)
|
||||
};
|
||||
|
||||
match result {
|
||||
Some(api) => {
|
||||
tracing::debug!("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 WebAuthNPluginAuthenticatorRemoveCredentials()\n{}",
|
||||
result.message()
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
None => {
|
||||
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(
|
||||
clsid_guid: GUID,
|
||||
) -> std::result::Result<Vec<OwnedCredentialDetails>, String> {
|
||||
tracing::debug!("Loading WebAuthNPluginAuthenticatorGetAllCredentials function...");
|
||||
|
||||
let result = unsafe {
|
||||
delay_load::<WebAuthNPluginAuthenticatorGetAllCredentialsFnDeclaration>(
|
||||
s!("webauthn.dll"),
|
||||
s!("WebAuthNPluginAuthenticatorGetAllCredentials"),
|
||||
)
|
||||
};
|
||||
|
||||
match result {
|
||||
Some(api) => {
|
||||
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 WebAuthNPluginAuthenticatorGetAllCredentials()\n{}",
|
||||
result.message()
|
||||
));
|
||||
}
|
||||
|
||||
if credentials_array_ptr.is_null() || credential_count == 0 {
|
||||
tracing::debug!("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);
|
||||
|
||||
tracing::debug!("Retrieved {} credentials", owned_credentials.len());
|
||||
Ok(owned_credentials)
|
||||
},
|
||||
None => {
|
||||
Err(String::from("Error: Can't complete get_all_credentials(), as the function WebAuthNPluginAuthenticatorGetAllCredentials can't be loaded."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn free_credential_details_array(
|
||||
credential_count: u32,
|
||||
credentials_array: *mut WebAuthnPluginCredentialDetails,
|
||||
) {
|
||||
if credentials_array.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let result = unsafe {
|
||||
delay_load::<WebAuthNPluginAuthenticatorFreeCredentialDetailsArrayFnDeclaration>(
|
||||
s!("webauthn.dll"),
|
||||
s!("WebAuthNPluginAuthenticatorFreeCredentialDetailsArray"),
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(api) = result {
|
||||
unsafe { api(credential_count, credentials_array) };
|
||||
} else {
|
||||
tracing::debug!(
|
||||
"Warning: Could not load WebAuthNPluginAuthenticatorFreeCredentialDetailsArray"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_all_credentials(clsid_guid: GUID) -> std::result::Result<(), String> {
|
||||
tracing::debug!("Loading WebAuthNPluginAuthenticatorRemoveAllCredentials function...");
|
||||
|
||||
let result = unsafe {
|
||||
delay_load::<WebAuthNPluginAuthenticatorRemoveAllCredentialsFnDeclaration>(
|
||||
s!("webauthn.dll"),
|
||||
s!("WebAuthNPluginAuthenticatorRemoveAllCredentials"),
|
||||
)
|
||||
};
|
||||
|
||||
match result {
|
||||
Some(api) => {
|
||||
tracing::debug!("Function loaded successfully, calling API...");
|
||||
|
||||
let result = unsafe { api(&clsid_guid) };
|
||||
|
||||
if result.is_err() {
|
||||
let error_code = result.0;
|
||||
tracing::debug!("API call failed with HRESULT: 0x{:x}", error_code);
|
||||
|
||||
return Err(format!(
|
||||
"Error: Error response from WebAuthNPluginAuthenticatorRemoveAllCredentials()\nHRESULT: 0x{:x}\n{}",
|
||||
error_code, result.message()
|
||||
));
|
||||
}
|
||||
|
||||
tracing::debug!("API call succeeded");
|
||||
Ok(())
|
||||
}
|
||||
None => {
|
||||
tracing::debug!("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."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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 ppCredentials: *const *const WEBAUTHN_CREDENTIAL_EX,
|
||||
}
|
||||
Reference in New Issue
Block a user