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:
@@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user