1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-09 13:10:17 +00:00

Define a framework for implementing plugin authenticator methods.

This commit is contained in:
Isaiah Inuwa
2025-11-19 23:57:36 -06:00
parent d345896d87
commit a5ccb5e25d
3 changed files with 288 additions and 16 deletions

View File

@@ -13,14 +13,21 @@ mod util;
mod webauthn;
mod win_webauthn;
use std::collections::HashSet;
use std::{collections::HashSet, sync::Arc, time::Duration};
// Re-export main functionality
pub use types::UserVerificationRequirement;
use win_webauthn::{PluginAddAuthenticatorOptions, WebAuthnPlugin};
use crate::win_webauthn::{AuthenticatorInfo, CtapVersion, PublicKeyCredentialParameters};
use crate::{
ipc2::{ConnectionStatus, TimedCallback, WindowsProviderClient},
win_webauthn::{
AuthenticatorInfo, CtapVersion, PluginAuthenticator, PluginCancelOperationRequest,
PluginGetAssertionRequest, PluginLockStatus, PluginMakeCredentialRequest,
PublicKeyCredentialParameters,
},
};
const AUTHENTICATOR_NAME: &str = "Bitwarden Desktop";
const RPID: &str = "bitwarden.com";
@@ -42,7 +49,8 @@ pub fn register() -> std::result::Result<(), String> {
let clsid = CLSID.try_into().expect("valid GUID string");
let plugin = WebAuthnPlugin::new(clsid);
let r = plugin.register_server();
let r = plugin.register_server(BitwardenPluginAuthenticator);
tracing::debug!("Registered the com library: {:?}", r);
tracing::debug!("Parsing authenticator options");
@@ -79,3 +87,56 @@ pub fn register() -> std::result::Result<(), String> {
Ok(())
}
struct BitwardenPluginAuthenticator;
impl BitwardenPluginAuthenticator {
fn get_client(&self) -> WindowsProviderClient {
tracing::debug!("Connecting to client via IPC");
let client = WindowsProviderClient::connect();
tracing::debug!("Connected to client via IPC successfully");
client
}
}
impl PluginAuthenticator for BitwardenPluginAuthenticator {
fn make_credential(
&self,
request: PluginMakeCredentialRequest,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
tracing::debug!("Received MakeCredential: {request:?}");
Err(format!("MakeCredential not implemented").into())
}
fn get_assertion(
&self,
request: PluginGetAssertionRequest,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
tracing::debug!("Received GetAssertion: {request:?}");
Err(format!("GetAssertion not implemented").into())
}
fn cancel_operation(
&self,
request: PluginCancelOperationRequest,
) -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
fn lock_status(&self) -> Result<PluginLockStatus, Box<dyn std::error::Error>> {
let callback = Arc::new(TimedCallback::new());
let client = self.get_client();
client.get_lock_status(callback.clone());
match callback.wait_for_response(Duration::from_secs(3)) {
Ok(Ok(response)) => {
if response.is_unlocked {
Ok(PluginLockStatus::PluginUnlocked)
} else {
Ok(PluginLockStatus::PluginLocked)
}
}
Ok(Err(err)) => Err(format!("GetLockStatus() call failed: {err}").into()),
Err(_) => Err(format!("GetLockStatus() call timed out").into()),
}
}
}

View File

@@ -1,14 +1,32 @@
//! Functions for interacting with Windows COM.
use std::ptr;
use std::{
alloc,
error::Error,
mem::MaybeUninit,
ptr::{self, NonNull},
sync::{Arc, OnceLock},
};
use windows::{
core::{implement, ComObjectInterface, IUnknown, GUID},
Win32::System::Com::*,
core::{implement, interface, ComObjectInterface, IUnknown, GUID, HRESULT},
Win32::{
Foundation::{E_FAIL, E_INVALIDARG, S_OK},
System::Com::*,
},
};
use windows_core::{IInspectable, Interface};
use crate::win_webauthn::types::{
PluginCancelOperationRequest, PluginGetAssertionRequest, PluginLockStatus,
PluginMakeCredentialRequest, WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST,
WEBAUTHN_PLUGIN_OPERATION_REQUEST, WEBAUTHN_PLUGIN_OPERATION_RESPONSE,
};
use super::{ErrorKind, WinWebAuthnError};
static HANDLER: OnceLock<Arc<dyn PluginAuthenticator + Send + Sync>> = OnceLock::new();
#[implement(IClassFactory)]
pub struct Factory;
@@ -16,21 +34,204 @@ impl IClassFactory_Impl for Factory_Impl {
fn CreateInstance(
&self,
_outer: windows::core::Ref<IUnknown>,
_iid: *const windows::core::GUID,
_object: *mut *mut core::ffi::c_void,
iid: *const windows::core::GUID,
object: *mut *mut core::ffi::c_void,
) -> windows::core::Result<()> {
unimplemented!()
let handler = match HANDLER.get() {
Some(handler) => handler,
None => {
tracing::error!("Cannot create COM class object instance because the handler is not initialized. register_server() must be called before starting the COM server.");
return Err(E_FAIL.into());
}
}.clone();
let unknown: IInspectable = PluginAuthenticatorComObject { handler }.into();
unsafe { unknown.query(iid, object).ok() }
}
fn LockServer(&self, _lock: windows::core::BOOL) -> windows::core::Result<()> {
unimplemented!()
// TODO: Implement lock server
Ok(())
}
}
pub trait PluginAuthenticator {
/// Process a request to create a new credential.
///
/// Returns a [CTAP authenticatorMakeCredential response structure](https://fidoalliance.org/specs/fido-v2.2-ps-20250714/fido-client-to-authenticator-protocol-v2.2-ps-20250714.html#authenticatormakecredential-response-structure).
fn make_credential(
&self,
request: PluginMakeCredentialRequest,
) -> Result<Vec<u8>, Box<dyn Error>>;
/// Process a request to assert a credential.
///
/// Returns a [CTAP authenticatorGetAssertion response structure](https://fidoalliance.org/specs/fido-v2.2-ps-20250714/fido-client-to-authenticator-protocol-v2.2-ps-20250714.html#authenticatorgetassertion-response-structure).
fn get_assertion(&self, request: PluginGetAssertionRequest) -> Result<Vec<u8>, Box<dyn Error>>;
/// Cancel an ongoing operation.
fn cancel_operation(&self, request: PluginCancelOperationRequest)
-> Result<(), Box<dyn Error>>;
/// Retrieve lock status.
fn lock_status(&self) -> Result<PluginLockStatus, Box<dyn Error>>;
}
// IPluginAuthenticator interface
#[interface("d26bcf6f-b54c-43ff-9f06-d5bf148625f7")]
pub unsafe trait IPluginAuthenticator: windows::core::IUnknown {
fn MakeCredential(
&self,
request: *const WEBAUTHN_PLUGIN_OPERATION_REQUEST,
response: *mut WEBAUTHN_PLUGIN_OPERATION_RESPONSE,
) -> HRESULT;
fn GetAssertion(
&self,
request: *const WEBAUTHN_PLUGIN_OPERATION_REQUEST,
response: *mut WEBAUTHN_PLUGIN_OPERATION_RESPONSE,
) -> HRESULT;
fn CancelOperation(&self, request: *const WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST) -> HRESULT;
fn GetLockStatus(&self, lock_status: *mut PluginLockStatus) -> HRESULT;
}
#[implement(IPluginAuthenticator)]
struct PluginAuthenticatorComObject {
handler: Arc<dyn PluginAuthenticator + Send + Sync>,
}
impl IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Impl {
unsafe fn MakeCredential(
&self,
request: *const WEBAUTHN_PLUGIN_OPERATION_REQUEST,
response: *mut WEBAUTHN_PLUGIN_OPERATION_RESPONSE,
) -> HRESULT {
tracing::debug!("MakeCredential called");
// Convert to legacy format for internal processing
if request.is_null() || response.is_null() {
tracing::debug!("MakeCredential: Invalid request or response pointers passed");
return HRESULT(-1);
}
// TODO: verify request signature
return HRESULT(-1);
/*
match self.handler.make_credential(request) {
Ok(response) => {
// todo DECODE
tracing::debug!("MakeCredential completed successfully");
S_OK
}
Err(err) => {
tracing::error!("MakeCredential failed: {err}");
HRESULT(-1)
}
}
*/
}
unsafe fn GetAssertion(
&self,
request: *const WEBAUTHN_PLUGIN_OPERATION_REQUEST,
response: *mut WEBAUTHN_PLUGIN_OPERATION_RESPONSE,
) -> HRESULT {
tracing::debug!("GetAssertion called");
if request.is_null() || response.is_null() {
tracing::warn!("GetAssertion called with invalid arguments");
return E_INVALIDARG;
}
// TODO: verify request signature
let assertion_request: PluginGetAssertionRequest =
match NonNull::new(request as *mut WEBAUTHN_PLUGIN_OPERATION_REQUEST)
.map(PluginGetAssertionRequest::try_from)
{
Some(Ok(assertion_request)) => assertion_request,
Some(Err(err)) => {
tracing::error!("Could not deserialize GetAssertion request: {err}");
return E_FAIL;
}
None => {
tracing::warn!("GetOperation received null request");
return E_INVALIDARG;
}
};
match self.handler.get_assertion(assertion_request) {
Ok(assertion_response) => {
let response = &mut *response;
response.cbEncodedResponse = assertion_response.len() as u32;
std::ptr::copy_nonoverlapping(
assertion_response.as_ptr(),
response.pbEncodedResponse,
assertion_response.len(),
);
tracing::error!("GetAssertion completed successfully");
S_OK
}
Err(err) => {
tracing::error!("GetAssertion failed: {err}");
E_FAIL
}
}
}
unsafe fn CancelOperation(
&self,
request: *const WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST,
) -> HRESULT {
tracing::debug!("CancelOperation called");
let request = match NonNull::new(request as *mut WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST) {
Some(request) => request,
None => {
tracing::warn!("Received null CancelOperation request");
return E_INVALIDARG;
}
};
match self.handler.cancel_operation(request.into()) {
Ok(()) => {
tracing::error!("CancelOperation completed successfully");
S_OK
}
Err(err) => {
tracing::error!("CancelOperation failed: {err}");
E_FAIL
}
}
}
unsafe fn GetLockStatus(&self, lock_status: *mut PluginLockStatus) -> HRESULT {
tracing::debug!(
"GetLockStatus() called <PID {}, Thread {:?}>",
std::process::id(),
std::thread::current().id()
);
if lock_status.is_null() {
return HRESULT(-2147024809); // E_INVALIDARG
}
match self.handler.lock_status() {
Ok(status) => {
tracing::debug!("GetLockStatus received {status:?}");
*lock_status = status;
S_OK
}
Err(err) => {
tracing::error!("GetLockStatus failed: {err}");
E_FAIL
}
}
}
}
/// Registers the plugin authenticator COM library with Windows.
pub(super) fn register_server(clsid: &GUID) -> Result<(), WinWebAuthnError> {
static FACTORY: windows::core::StaticComObject<crate::com_provider::Factory> =
crate::com_provider::Factory.into_static();
pub(super) fn register_server<T>(clsid: &GUID, handler: T) -> Result<(), WinWebAuthnError>
where
T: PluginAuthenticator + Send + Sync + 'static,
{
// Store the handler as a static so it can be initialized
HANDLER.set(Arc::new(handler)).map_err(|_| {
WinWebAuthnError::new(ErrorKind::WindowsInternal, "Handler already initialized")
})?;
static FACTORY: windows::core::StaticComObject<Factory> = Factory.into_static();
unsafe {
CoRegisterClassObject(
ptr::from_ref(clsid),

View File

@@ -7,9 +7,13 @@ use std::{error::Error, fmt::Display, ptr::NonNull};
use windows::core::GUID;
pub use types::{
AuthenticatorInfo, CtapVersion, PluginAddAuthenticatorOptions, PublicKeyCredentialParameters,
AuthenticatorInfo, CtapVersion, PluginAddAuthenticatorOptions, PluginCancelOperationRequest,
PluginGetAssertionRequest, PluginLockStatus, PluginMakeCredentialRequest,
PublicKeyCredentialParameters,
};
pub use com::PluginAuthenticator;
use crate::win_webauthn::{
types::{
webauthn_plugin_add_authenticator, PluginAddAuthenticatorResponse,
@@ -59,9 +63,15 @@ impl WebAuthnPlugin {
/// Registers a COM server with Windows.
///
/// The handler should be an instance of your type that implements PluginAuthenticator.
/// The same instance will be shared across all COM calls.
///
/// This only needs to be called on installation of your application.
pub fn register_server(&self) -> Result<(), WinWebAuthnError> {
com::register_server(&self.clsid.0)
pub fn register_server<T>(&self, handler: T) -> Result<(), WinWebAuthnError>
where
T: PluginAuthenticator + Send + Sync + 'static,
{
com::register_server(&self.clsid.0, handler)
}
/// Initializes the COM library for use on the calling thread,