mirror of
https://github.com/bitwarden/browser
synced 2026-02-01 09:13:54 +00:00
Reorganize modules
This commit is contained in:
@@ -7,7 +7,7 @@ use crate::{
|
||||
PasskeyAssertionRequest, PasskeyAssertionResponse, Position, TimedCallback,
|
||||
UserVerification, WindowsProviderClient,
|
||||
},
|
||||
win_webauthn::{ErrorKind, HwndExt, PluginGetAssertionRequest, WinWebAuthnError},
|
||||
win_webauthn::{plugin::PluginGetAssertionRequest, ErrorKind, HwndExt, WinWebAuthnError},
|
||||
};
|
||||
|
||||
pub fn get_assertion(
|
||||
|
||||
@@ -18,15 +18,17 @@ use std::{collections::HashSet, sync::Arc, time::Duration};
|
||||
// Re-export main functionality
|
||||
pub use types::UserVerificationRequirement;
|
||||
|
||||
use win_webauthn::{PluginAddAuthenticatorOptions, WebAuthnPlugin};
|
||||
use win_webauthn::plugin::{PluginAddAuthenticatorOptions, WebAuthnPlugin};
|
||||
|
||||
use crate::{
|
||||
ipc2::{ConnectionStatus, TimedCallback, WindowsProviderClient},
|
||||
make_credential::make_credential,
|
||||
win_webauthn::{
|
||||
AuthenticatorInfo, CtapVersion, PluginAuthenticator, PluginCancelOperationRequest,
|
||||
PluginGetAssertionRequest, PluginLockStatus, PluginMakeCredentialRequest,
|
||||
PublicKeyCredentialParameters,
|
||||
plugin::{
|
||||
PluginAuthenticator, PluginCancelOperationRequest, PluginGetAssertionRequest,
|
||||
PluginLockStatus, PluginMakeCredentialRequest,
|
||||
},
|
||||
AuthenticatorInfo, CtapVersion, PublicKeyCredentialParameters,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ use crate::ipc2::{
|
||||
UserVerification, WindowsProviderClient,
|
||||
};
|
||||
use crate::win_webauthn::{
|
||||
CtapTransport, ErrorKind, HwndExt, PluginMakeCredentialRequest, PluginMakeCredentialResponse,
|
||||
WinWebAuthnError,
|
||||
plugin::{PluginMakeCredentialRequest, PluginMakeCredentialResponse},
|
||||
CtapTransport, ErrorKind, HwndExt, WinWebAuthnError,
|
||||
};
|
||||
|
||||
pub fn make_credential(
|
||||
|
||||
@@ -1,153 +1,14 @@
|
||||
mod com;
|
||||
pub mod plugin;
|
||||
mod types;
|
||||
mod util;
|
||||
|
||||
use std::{error::Error, fmt::Display, ptr::NonNull};
|
||||
use std::{error::Error, fmt::Display};
|
||||
|
||||
use windows::core::GUID;
|
||||
pub use types::{AuthenticatorInfo, CtapTransport, CtapVersion, PublicKeyCredentialParameters};
|
||||
|
||||
pub use types::{
|
||||
AuthenticatorInfo, CtapTransport, CtapVersion, PluginAddAuthenticatorOptions,
|
||||
PluginCancelOperationRequest, PluginGetAssertionRequest, PluginLockStatus,
|
||||
PluginMakeCredentialRequest, PluginMakeCredentialResponse, PublicKeyCredentialParameters,
|
||||
};
|
||||
|
||||
pub use com::PluginAuthenticator;
|
||||
use plugin::PluginAuthenticator;
|
||||
pub use util::HwndExt;
|
||||
|
||||
use crate::win_webauthn::{
|
||||
types::{
|
||||
webauthn_plugin_add_authenticator, PluginAddAuthenticatorResponse,
|
||||
WebAuthnPluginAddAuthenticatorResponse, WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS,
|
||||
},
|
||||
util::WindowsString,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Clsid(GUID);
|
||||
|
||||
impl TryFrom<&str> for Clsid {
|
||||
type Error = WinWebAuthnError;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
// Remove hyphens and parse as hex
|
||||
let clsid_clean = value.replace("-", "").replace("{", "").replace("}", "");
|
||||
if clsid_clean.len() != 32 {
|
||||
return Err(WinWebAuthnError::new(
|
||||
ErrorKind::Serialization,
|
||||
"Invalid CLSID format",
|
||||
));
|
||||
}
|
||||
|
||||
// Convert to u128 and create GUID
|
||||
let clsid_u128 = u128::from_str_radix(&clsid_clean, 16).map_err(|err| {
|
||||
WinWebAuthnError::with_cause(
|
||||
ErrorKind::Serialization,
|
||||
"Failed to parse CLSID as hex",
|
||||
err,
|
||||
)
|
||||
})?;
|
||||
|
||||
let clsid = Clsid(GUID::from_u128(clsid_u128));
|
||||
Ok(clsid)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WebAuthnPlugin {
|
||||
clsid: Clsid,
|
||||
}
|
||||
|
||||
impl WebAuthnPlugin {
|
||||
pub fn new(clsid: Clsid) -> Self {
|
||||
WebAuthnPlugin { clsid }
|
||||
}
|
||||
|
||||
/// 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<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,
|
||||
/// and registers + sets the security values.
|
||||
pub fn initialize() -> Result<(), WinWebAuthnError> {
|
||||
com::initialize()
|
||||
}
|
||||
|
||||
/// Adds this implementation as a Windows WebAuthn plugin.
|
||||
///
|
||||
/// This only needs to be called on installation of your application.
|
||||
pub fn add_authenticator(
|
||||
options: PluginAddAuthenticatorOptions,
|
||||
) -> Result<PluginAddAuthenticatorResponse, WinWebAuthnError> {
|
||||
let mut response_ptr: *mut WebAuthnPluginAddAuthenticatorResponse = std::ptr::null_mut();
|
||||
|
||||
// We need to be careful to use .as_ref() to ensure that we're not
|
||||
// sending dangling pointers to API.
|
||||
let authenticator_name = options.authenticator_name.to_utf16();
|
||||
|
||||
let rp_id = options.rp_id.as_ref().map(|rp_id| rp_id.to_utf16());
|
||||
let pwszPluginRpId = rp_id.as_ref().map_or(std::ptr::null(), |v| v.as_ptr());
|
||||
|
||||
let light_logo_b64 = options.light_theme_logo_b64();
|
||||
let pwszLightThemeLogoSvg = light_logo_b64
|
||||
.as_ref()
|
||||
.map_or(std::ptr::null(), |v| v.as_ptr());
|
||||
let dark_logo_b64 = options.dark_theme_logo_b64();
|
||||
let pwszDarkThemeLogoSvg = dark_logo_b64
|
||||
.as_ref()
|
||||
.map_or(std::ptr::null(), |v| v.as_ptr());
|
||||
|
||||
let authenticator_info = options.authenticator_info.as_ctap_bytes()?;
|
||||
|
||||
let supported_rp_ids: Option<Vec<Vec<u16>>> = options
|
||||
.supported_rp_ids
|
||||
.map(|ids| ids.iter().map(|id| id.to_utf16()).collect());
|
||||
let supported_rp_id_ptrs: Option<Vec<*const u16>> = supported_rp_ids
|
||||
.as_ref()
|
||||
.map(|ids| ids.iter().map(Vec::as_ptr).collect());
|
||||
let pbSupportedRpIds = supported_rp_id_ptrs
|
||||
.as_ref()
|
||||
.map_or(std::ptr::null(), |v| v.as_ptr());
|
||||
|
||||
let options_c = WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS {
|
||||
pwszAuthenticatorName: authenticator_name.as_ptr(),
|
||||
rclsid: &options.clsid.0,
|
||||
pwszPluginRpId,
|
||||
pwszLightThemeLogoSvg,
|
||||
pwszDarkThemeLogoSvg,
|
||||
cbAuthenticatorInfo: authenticator_info.len() as u32,
|
||||
pbAuthenticatorInfo: authenticator_info.as_ptr(),
|
||||
cSupportedRpIds: supported_rp_id_ptrs.map_or(0, |ids| ids.len() as u32),
|
||||
pbSupportedRpIds,
|
||||
};
|
||||
let result = webauthn_plugin_add_authenticator(&options_c, &mut response_ptr)?;
|
||||
result.ok().map_err(|err| {
|
||||
WinWebAuthnError::with_cause(
|
||||
ErrorKind::WindowsInternal,
|
||||
"Failed to add authenticator",
|
||||
err,
|
||||
)
|
||||
})?;
|
||||
|
||||
if let Some(response) = NonNull::new(response_ptr) {
|
||||
Ok(response.into())
|
||||
} else {
|
||||
Err(WinWebAuthnError::new(
|
||||
ErrorKind::WindowsInternal,
|
||||
"WebAuthNPluginAddAuthenticatorResponse returned null",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WinWebAuthnError {
|
||||
kind: ErrorKind,
|
||||
@@ -204,16 +65,3 @@ impl Display for WinWebAuthnError {
|
||||
}
|
||||
|
||||
impl Error for WinWebAuthnError {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Clsid;
|
||||
|
||||
const CLSID: &str = "0f7dc5d9-69ce-4652-8572-6877fd695062";
|
||||
|
||||
#[test]
|
||||
fn test_parse_clsid_to_guid() {
|
||||
let result = Clsid::try_from(CLSID);
|
||||
assert!(result.is_ok(), "CLSID parsing should succeed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,14 @@ use windows::{
|
||||
};
|
||||
use windows_core::{IInspectable, Interface};
|
||||
|
||||
use crate::win_webauthn::types::{
|
||||
use super::types::{
|
||||
PluginCancelOperationRequest, PluginGetAssertionRequest, PluginLockStatus,
|
||||
PluginMakeCredentialRequest, WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST,
|
||||
WEBAUTHN_PLUGIN_OPERATION_REQUEST, WEBAUTHN_PLUGIN_OPERATION_RESPONSE,
|
||||
};
|
||||
|
||||
use super::{ErrorKind, WinWebAuthnError};
|
||||
use super::PluginAuthenticator;
|
||||
use crate::win_webauthn::{ErrorKind, WinWebAuthnError};
|
||||
|
||||
static HANDLER: OnceLock<Arc<dyn PluginAuthenticator + Send + Sync>> = OnceLock::new();
|
||||
|
||||
@@ -54,28 +55,6 @@ impl IClassFactory_Impl for Factory_Impl {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -0,0 +1,173 @@
|
||||
pub(crate) mod com;
|
||||
pub(crate) mod types;
|
||||
|
||||
use std::{error::Error, ptr::NonNull};
|
||||
use types::*;
|
||||
use windows::core::GUID;
|
||||
|
||||
pub use types::{
|
||||
PluginAddAuthenticatorOptions, PluginAddAuthenticatorResponse, PluginCancelOperationRequest,
|
||||
PluginGetAssertionRequest, PluginLockStatus, PluginMakeCredentialRequest,
|
||||
PluginMakeCredentialResponse,
|
||||
};
|
||||
|
||||
use super::{ErrorKind, WinWebAuthnError};
|
||||
use crate::win_webauthn::util::WindowsString;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Clsid(GUID);
|
||||
|
||||
impl TryFrom<&str> for Clsid {
|
||||
type Error = WinWebAuthnError;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
// Remove hyphens and parse as hex
|
||||
let clsid_clean = value.replace("-", "").replace("{", "").replace("}", "");
|
||||
if clsid_clean.len() != 32 {
|
||||
return Err(WinWebAuthnError::new(
|
||||
ErrorKind::Serialization,
|
||||
"Invalid CLSID format",
|
||||
));
|
||||
}
|
||||
|
||||
// Convert to u128 and create GUID
|
||||
let clsid_u128 = u128::from_str_radix(&clsid_clean, 16).map_err(|err| {
|
||||
WinWebAuthnError::with_cause(
|
||||
ErrorKind::Serialization,
|
||||
"Failed to parse CLSID as hex",
|
||||
err,
|
||||
)
|
||||
})?;
|
||||
|
||||
let clsid = Clsid(GUID::from_u128(clsid_u128));
|
||||
Ok(clsid)
|
||||
}
|
||||
}
|
||||
pub struct WebAuthnPlugin {
|
||||
clsid: Clsid,
|
||||
}
|
||||
|
||||
impl WebAuthnPlugin {
|
||||
pub fn new(clsid: Clsid) -> Self {
|
||||
WebAuthnPlugin { clsid }
|
||||
}
|
||||
|
||||
/// 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<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,
|
||||
/// and registers + sets the security values.
|
||||
pub fn initialize() -> Result<(), WinWebAuthnError> {
|
||||
com::initialize()
|
||||
}
|
||||
|
||||
/// Adds this implementation as a Windows WebAuthn plugin.
|
||||
///
|
||||
/// This only needs to be called on installation of your application.
|
||||
pub fn add_authenticator(
|
||||
options: PluginAddAuthenticatorOptions,
|
||||
) -> Result<PluginAddAuthenticatorResponse, WinWebAuthnError> {
|
||||
let mut response_ptr: *mut WebAuthnPluginAddAuthenticatorResponse = std::ptr::null_mut();
|
||||
|
||||
// We need to be careful to use .as_ref() to ensure that we're not
|
||||
// sending dangling pointers to API.
|
||||
let authenticator_name = options.authenticator_name.to_utf16();
|
||||
|
||||
let rp_id = options.rp_id.as_ref().map(|rp_id| rp_id.to_utf16());
|
||||
let pwszPluginRpId = rp_id.as_ref().map_or(std::ptr::null(), |v| v.as_ptr());
|
||||
|
||||
let light_logo_b64 = options.light_theme_logo_b64();
|
||||
let pwszLightThemeLogoSvg = light_logo_b64
|
||||
.as_ref()
|
||||
.map_or(std::ptr::null(), |v| v.as_ptr());
|
||||
let dark_logo_b64 = options.dark_theme_logo_b64();
|
||||
let pwszDarkThemeLogoSvg = dark_logo_b64
|
||||
.as_ref()
|
||||
.map_or(std::ptr::null(), |v| v.as_ptr());
|
||||
|
||||
let authenticator_info = options.authenticator_info.as_ctap_bytes()?;
|
||||
|
||||
let supported_rp_ids: Option<Vec<Vec<u16>>> = options
|
||||
.supported_rp_ids
|
||||
.map(|ids| ids.iter().map(|id| id.to_utf16()).collect());
|
||||
let supported_rp_id_ptrs: Option<Vec<*const u16>> = supported_rp_ids
|
||||
.as_ref()
|
||||
.map(|ids| ids.iter().map(Vec::as_ptr).collect());
|
||||
let pbSupportedRpIds = supported_rp_id_ptrs
|
||||
.as_ref()
|
||||
.map_or(std::ptr::null(), |v| v.as_ptr());
|
||||
|
||||
let options_c = WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS {
|
||||
pwszAuthenticatorName: authenticator_name.as_ptr(),
|
||||
rclsid: &options.clsid.0,
|
||||
pwszPluginRpId,
|
||||
pwszLightThemeLogoSvg,
|
||||
pwszDarkThemeLogoSvg,
|
||||
cbAuthenticatorInfo: authenticator_info.len() as u32,
|
||||
pbAuthenticatorInfo: authenticator_info.as_ptr(),
|
||||
cSupportedRpIds: supported_rp_id_ptrs.map_or(0, |ids| ids.len() as u32),
|
||||
pbSupportedRpIds,
|
||||
};
|
||||
let result = webauthn_plugin_add_authenticator(&options_c, &mut response_ptr)?;
|
||||
result.ok().map_err(|err| {
|
||||
WinWebAuthnError::with_cause(
|
||||
ErrorKind::WindowsInternal,
|
||||
"Failed to add authenticator",
|
||||
err,
|
||||
)
|
||||
})?;
|
||||
|
||||
if let Some(response) = NonNull::new(response_ptr) {
|
||||
Ok(response.into())
|
||||
} else {
|
||||
Err(WinWebAuthnError::new(
|
||||
ErrorKind::WindowsInternal,
|
||||
"WebAuthNPluginAddAuthenticatorResponse returned null",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
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>>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Clsid;
|
||||
|
||||
const CLSID: &str = "0f7dc5d9-69ce-4652-8572-6877fd695062";
|
||||
|
||||
#[test]
|
||||
fn test_parse_clsid_to_guid() {
|
||||
let result = Clsid::try_from(CLSID);
|
||||
assert!(result.is_ok(), "CLSID parsing should succeed");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,818 @@
|
||||
//! Types pertaining to registering a plugin implementation and handling plugin
|
||||
//! authenticator requests.
|
||||
|
||||
use std::{collections::HashSet, fmt::Display, mem::MaybeUninit, ptr::NonNull};
|
||||
|
||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||
use ciborium::Value;
|
||||
use windows::{
|
||||
core::{GUID, HRESULT},
|
||||
Win32::{
|
||||
Foundation::HWND, System::LibraryLoader::GetProcAddress,
|
||||
UI::WindowsAndMessaging::WindowFromPoint,
|
||||
},
|
||||
};
|
||||
use windows_core::{s, PCWSTR};
|
||||
|
||||
use crate::win_webauthn::{
|
||||
util::{webauthn_call, ArrayPointerIterator, WindowsString},
|
||||
ErrorKind, WinWebAuthnError,
|
||||
};
|
||||
|
||||
use crate::win_webauthn::types::{
|
||||
AuthenticatorInfo, CredentialList, CtapTransport, HmacSecretSalt,
|
||||
WebAuthnExtensionMakeCredentialOutput, WEBAUTHN_COSE_CREDENTIAL_PARAMETERS,
|
||||
WEBAUTHN_CREDENTIAL_ATTESTATION, WEBAUTHN_CREDENTIAL_LIST, WEBAUTHN_EXTENSIONS,
|
||||
WEBAUTHN_RP_ENTITY_INFORMATION, WEBAUTHN_USER_ENTITY_INFORMATION,
|
||||
};
|
||||
|
||||
use super::Clsid;
|
||||
|
||||
// Plugin Registration types
|
||||
|
||||
/// Windows WebAuthn Authenticator Options structure
|
||||
/// Header File Name: _WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct WebAuthnCtapCborAuthenticatorOptions {
|
||||
dwVersion: u32,
|
||||
// LONG lUp: +1=TRUE, 0=Not defined, -1=FALSE
|
||||
lUp: i32,
|
||||
// LONG lUv: +1=TRUE, 0=Not defined, -1=FALSE
|
||||
lUv: i32,
|
||||
// LONG lRequireResidentKey: +1=TRUE, 0=Not defined, -1=FALSE
|
||||
lRequireResidentKey: i32,
|
||||
}
|
||||
|
||||
impl WebAuthnCtapCborAuthenticatorOptions {
|
||||
pub fn version(&self) -> u32 {
|
||||
self.dwVersion
|
||||
}
|
||||
|
||||
pub fn user_presence(&self) -> Option<bool> {
|
||||
Self::to_optional_bool(self.lUp)
|
||||
}
|
||||
|
||||
pub fn user_verification(&self) -> Option<bool> {
|
||||
Self::to_optional_bool(self.lUv)
|
||||
}
|
||||
|
||||
pub fn require_resident_key(&self) -> Option<bool> {
|
||||
Self::to_optional_bool(self.lRequireResidentKey)
|
||||
}
|
||||
|
||||
fn to_optional_bool(value: i32) -> Option<bool> {
|
||||
match value {
|
||||
x if x > 0 => Some(true),
|
||||
x if x < 0 => Some(false),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
type WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS = WebAuthnCtapCborAuthenticatorOptions;
|
||||
|
||||
/// 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(crate) struct WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS {
|
||||
/// Authenticator Name
|
||||
pub(super) pwszAuthenticatorName: *const u16,
|
||||
|
||||
/// Plugin COM ClsId
|
||||
pub(super) rclsid: *const GUID,
|
||||
|
||||
/// Plugin RPID
|
||||
///
|
||||
/// Required for a nested WebAuthN call originating from a plugin.
|
||||
pub(super) pwszPluginRpId: *const u16,
|
||||
|
||||
/// Plugin Authenticator Logo for the Light themes. base64-encoded SVG 1.1
|
||||
///
|
||||
/// The data should be encoded as `UTF16(BASE64(UTF8(svg_text)))`.
|
||||
pub(super) pwszLightThemeLogoSvg: *const u16,
|
||||
|
||||
/// Plugin Authenticator Logo for the Dark themes. base64-encoded SVG 1.1
|
||||
///
|
||||
/// The data should be encoded as `UTF16(BASE64(UTF8(svg_text)))`.
|
||||
pub(super) pwszDarkThemeLogoSvg: *const u16,
|
||||
|
||||
pub(super) cbAuthenticatorInfo: u32,
|
||||
/// CTAP CBOR-encoded authenticatorGetInfo output
|
||||
pub(super) pbAuthenticatorInfo: *const u8,
|
||||
|
||||
pub(super) cSupportedRpIds: u32,
|
||||
/// List of supported RP IDs (Relying Party IDs).
|
||||
///
|
||||
/// Should be null if all RPs are supported.
|
||||
pub(super) pbSupportedRpIds: *const *const u16,
|
||||
}
|
||||
|
||||
pub struct PluginAddAuthenticatorOptions {
|
||||
/// Authenticator Name
|
||||
pub authenticator_name: String,
|
||||
|
||||
/// Plugin COM ClsId
|
||||
pub clsid: Clsid,
|
||||
|
||||
/// Plugin RPID
|
||||
///
|
||||
/// Required for a nested WebAuthN call originating from a plugin.
|
||||
pub rp_id: Option<String>,
|
||||
|
||||
/// Plugin Authenticator Logo for the Light themes.
|
||||
///
|
||||
/// String should contain a valid SVG 1.1 document.
|
||||
pub light_theme_logo_svg: Option<String>,
|
||||
|
||||
// Plugin Authenticator Logo for the Dark themes. Bytes of SVG 1.1.
|
||||
///
|
||||
/// String should contain a valid SVG 1.1 element.
|
||||
pub dark_theme_logo_svg: Option<String>,
|
||||
|
||||
/// CTAP authenticatorGetInfo values
|
||||
pub authenticator_info: AuthenticatorInfo,
|
||||
|
||||
/// List of supported RP IDs (Relying Party IDs).
|
||||
///
|
||||
/// Should be [None] if all RPs are supported.
|
||||
pub supported_rp_ids: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl PluginAddAuthenticatorOptions {
|
||||
pub fn light_theme_logo_b64(&self) -> Option<Vec<u16>> {
|
||||
self.light_theme_logo_svg
|
||||
.as_ref()
|
||||
.map(|svg| Self::encode_svg(&svg))
|
||||
}
|
||||
|
||||
pub fn dark_theme_logo_b64(&self) -> Option<Vec<u16>> {
|
||||
self.dark_theme_logo_svg
|
||||
.as_ref()
|
||||
.map(|svg| Self::encode_svg(&svg))
|
||||
}
|
||||
|
||||
fn encode_svg(svg: &str) -> Vec<u16> {
|
||||
let logo_b64: String = STANDARD.encode(svg);
|
||||
logo_b64.to_utf16()
|
||||
}
|
||||
}
|
||||
|
||||
/// Used as a response type when adding a Windows plugin authenticator.
|
||||
/// Header File Name: _WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE
|
||||
/// Header File Usage: WebAuthNPluginAddAuthenticator()
|
||||
/// WebAuthNPluginFreeAddAuthenticatorResponse()
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(super) struct WebAuthnPluginAddAuthenticatorResponse {
|
||||
pub plugin_operation_signing_key_byte_count: u32,
|
||||
pub plugin_operation_signing_key: *mut u8,
|
||||
}
|
||||
|
||||
type WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE = WebAuthnPluginAddAuthenticatorResponse;
|
||||
|
||||
/// Safe wrapper around [WebAuthnPluginAddAuthenticatorResponse]
|
||||
#[derive(Debug)]
|
||||
pub struct PluginAddAuthenticatorResponse {
|
||||
inner: NonNull<WebAuthnPluginAddAuthenticatorResponse>,
|
||||
}
|
||||
|
||||
impl PluginAddAuthenticatorResponse {
|
||||
pub fn plugin_operation_signing_key(&self) -> &[u8] {
|
||||
unsafe {
|
||||
let p = &*self.inner.as_ptr();
|
||||
std::slice::from_raw_parts(
|
||||
p.plugin_operation_signing_key,
|
||||
p.plugin_operation_signing_key_byte_count as usize,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NonNull<WebAuthnPluginAddAuthenticatorResponse>> for PluginAddAuthenticatorResponse {
|
||||
fn from(value: NonNull<WebAuthnPluginAddAuthenticatorResponse>) -> Self {
|
||||
Self { inner: value }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PluginAddAuthenticatorResponse {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// SAFETY: This should only fail if:
|
||||
// - we cannot load the webauthn.dll, which we already have if we have constructed this type, or
|
||||
// - we spelled the function wrong, which is a library error.
|
||||
webauthn_plugin_free_add_authenticator_response(self.inner.as_mut())
|
||||
.expect("function to load properly");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
webauthn_call!("WebAuthNPluginAddAuthenticator" as
|
||||
fn webauthn_plugin_add_authenticator(
|
||||
pPluginAddAuthenticatorOptions: *const WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS,
|
||||
ppPluginAddAuthenticatorResponse: *mut *mut WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE
|
||||
) -> HRESULT);
|
||||
|
||||
webauthn_call!("WebAuthNPluginFreeAddAuthenticatorResponse" as
|
||||
fn webauthn_plugin_free_add_authenticator_response(
|
||||
pPluginAddAuthenticatorOptions: *mut WebAuthnPluginAddAuthenticatorResponse
|
||||
) -> ());
|
||||
|
||||
/// Used when creating and asserting credentials.
|
||||
/// Header File Name: _WEBAUTHN_PLUGIN_OPERATION_REQUEST
|
||||
/// Header File Usage: MakeCredential()
|
||||
/// GetAssertion()
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(super) struct WEBAUTHN_PLUGIN_OPERATION_REQUEST {
|
||||
/// Window handle to client that requesting a WebAuthn credential.
|
||||
pub hWnd: HWND,
|
||||
pub transactionId: GUID,
|
||||
pub cbRequestSignature: u32,
|
||||
/// Signature over request made with the signing key created during authenticator registration.
|
||||
pub pbRequestSignature: *mut u8,
|
||||
pub requestType: WebAuthnPluginRequestType,
|
||||
pub cbEncodedRequest: u32,
|
||||
pub pbEncodedRequest: *const u8,
|
||||
}
|
||||
|
||||
/// Used as a response when creating and asserting credentials.
|
||||
/// Header File Name: _WEBAUTHN_PLUGIN_OPERATION_RESPONSE
|
||||
/// Header File Usage: MakeCredential()
|
||||
/// GetAssertion()
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) struct WEBAUTHN_PLUGIN_OPERATION_RESPONSE {
|
||||
pub cbEncodedResponse: u32,
|
||||
pub pbEncodedResponse: *mut u8,
|
||||
}
|
||||
|
||||
/// Plugin request type enum as defined in the IDL
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum WebAuthnPluginRequestType {
|
||||
CTAP2_CBOR = 0x01,
|
||||
}
|
||||
|
||||
// MakeCredential types
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST {
|
||||
pub dwVersion: u32,
|
||||
pub cbRpId: u32,
|
||||
pub pbRpId: *const u8,
|
||||
pub cbClientDataHash: u32,
|
||||
pub pbClientDataHash: *const u8,
|
||||
pub pRpInformation: *const WEBAUTHN_RP_ENTITY_INFORMATION,
|
||||
pub pUserInformation: *const WEBAUTHN_USER_ENTITY_INFORMATION,
|
||||
pub WebAuthNCredentialParameters: WEBAUTHN_COSE_CREDENTIAL_PARAMETERS, // Matches C++ sample
|
||||
pub CredentialList: WEBAUTHN_CREDENTIAL_LIST,
|
||||
pub cbCborExtensionsMap: u32,
|
||||
pub pbCborExtensionsMap: *const u8,
|
||||
pub pAuthenticatorOptions: *const WebAuthnCtapCborAuthenticatorOptions,
|
||||
// Add other fields as needed...
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PluginMakeCredentialRequest {
|
||||
inner: *const WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST,
|
||||
pub window_handle: HWND,
|
||||
pub transaction_id: GUID,
|
||||
pub request_signature: Vec<u8>,
|
||||
}
|
||||
|
||||
impl PluginMakeCredentialRequest {
|
||||
pub fn client_data_hash(&self) -> Result<&[u8], WinWebAuthnError> {
|
||||
if self.as_ref().cbClientDataHash == 0 || self.as_ref().pbClientDataHash.is_null() {
|
||||
return Err(WinWebAuthnError::new(
|
||||
ErrorKind::WindowsInternal,
|
||||
"Received invalid client data hash",
|
||||
));
|
||||
}
|
||||
unsafe {
|
||||
Ok(std::slice::from_raw_parts(
|
||||
self.as_ref().pbClientDataHash,
|
||||
self.as_ref().cbClientDataHash as usize,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rp_information(&self) -> Option<&WEBAUTHN_RP_ENTITY_INFORMATION> {
|
||||
let ptr = self.as_ref().pRpInformation;
|
||||
if ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
unsafe { Some(&*ptr) }
|
||||
}
|
||||
|
||||
pub fn user_information(&self) -> Option<&WEBAUTHN_USER_ENTITY_INFORMATION> {
|
||||
let ptr = self.as_ref().pUserInformation;
|
||||
if ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
unsafe { Some(&*ptr) }
|
||||
}
|
||||
|
||||
pub fn pub_key_cred_params(&self) -> WEBAUTHN_COSE_CREDENTIAL_PARAMETERS {
|
||||
self.as_ref().WebAuthNCredentialParameters
|
||||
}
|
||||
|
||||
pub fn exclude_credentials(&self) -> CredentialList {
|
||||
self.as_ref().CredentialList
|
||||
}
|
||||
|
||||
/// CTAP CBOR extensions map
|
||||
pub fn extensions(&self) -> Option<&[u8]> {
|
||||
let (len, ptr) = (
|
||||
self.as_ref().cbCborExtensionsMap,
|
||||
self.as_ref().pbCborExtensionsMap,
|
||||
);
|
||||
if len == 0 || ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
unsafe { Some(std::slice::from_raw_parts(ptr, len as usize)) }
|
||||
}
|
||||
|
||||
pub fn authenticator_options(&self) -> Option<WebAuthnCtapCborAuthenticatorOptions> {
|
||||
let ptr = self.as_ref().pAuthenticatorOptions;
|
||||
if ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
unsafe { Some(*ptr) }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST> for PluginMakeCredentialRequest {
|
||||
fn as_ref(&self) -> &WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST {
|
||||
unsafe { &*self.inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PluginMakeCredentialRequest {
|
||||
fn drop(&mut self) {
|
||||
if !self.inner.is_null() {
|
||||
// leak memory if we cannot find the free function
|
||||
_ = webauthn_free_decoded_make_credential_request(
|
||||
self.inner as *mut WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<NonNull<WEBAUTHN_PLUGIN_OPERATION_REQUEST>> for PluginMakeCredentialRequest {
|
||||
type Error = WinWebAuthnError;
|
||||
|
||||
fn try_from(value: NonNull<WEBAUTHN_PLUGIN_OPERATION_REQUEST>) -> Result<Self, Self::Error> {
|
||||
unsafe {
|
||||
let request = value.as_ref();
|
||||
if !matches!(request.requestType, WebAuthnPluginRequestType::CTAP2_CBOR) {
|
||||
return Err(WinWebAuthnError::new(
|
||||
ErrorKind::Serialization,
|
||||
"Unknown plugin operation request type",
|
||||
));
|
||||
}
|
||||
let mut registration_request = MaybeUninit::uninit();
|
||||
webauthn_decode_make_credential_request(
|
||||
request.cbEncodedRequest,
|
||||
request.pbEncodedRequest,
|
||||
registration_request.as_mut_ptr(),
|
||||
)?
|
||||
.ok()
|
||||
.map_err(|err| {
|
||||
WinWebAuthnError::with_cause(
|
||||
ErrorKind::WindowsInternal,
|
||||
"Failed to decode get assertion request",
|
||||
err,
|
||||
)
|
||||
})?;
|
||||
|
||||
let registration_request = registration_request.assume_init();
|
||||
Ok(Self {
|
||||
inner: registration_request as *const WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST,
|
||||
window_handle: request.hWnd,
|
||||
transaction_id: request.transactionId,
|
||||
request_signature: Vec::from_raw_parts(
|
||||
request.pbRequestSignature,
|
||||
request.cbEncodedRequest as usize,
|
||||
request.cbEncodedRequest as usize,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Windows API function signatures for decoding make credential requests
|
||||
webauthn_call!("WebAuthNDecodeMakeCredentialRequest" as fn webauthn_decode_make_credential_request(
|
||||
cbEncoded: u32,
|
||||
pbEncoded: *const u8,
|
||||
ppMakeCredentialRequest: *mut *mut WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST
|
||||
) -> HRESULT);
|
||||
|
||||
webauthn_call!("WebAuthNFreeDecodedMakeCredentialRequest" as fn webauthn_free_decoded_make_credential_request(
|
||||
pMakeCredentialRequest: *mut WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST
|
||||
) -> ());
|
||||
|
||||
pub struct PluginMakeCredentialResponse {
|
||||
/// Attestation format type
|
||||
pub format_type: String, // PCWSTR
|
||||
|
||||
/// Authenticator data that was created for this credential.
|
||||
pub authenticator_data: Vec<u8>,
|
||||
|
||||
///Encoded CBOR attestation information
|
||||
pub attestation_statement: Option<Vec<u8>>,
|
||||
|
||||
// dwAttestationDecodeType: u32,
|
||||
/// Following depends on the dwAttestationDecodeType
|
||||
/// WEBAUTHN_ATTESTATION_DECODE_NONE
|
||||
/// NULL - not able to decode the CBOR attestation information
|
||||
/// WEBAUTHN_ATTESTATION_DECODE_COMMON
|
||||
/// PWEBAUTHN_COMMON_ATTESTATION;
|
||||
// pub pvAttestationDecode: *mut u8,
|
||||
|
||||
/// The CBOR-encoded Attestation Object to be returned to the RP.
|
||||
pub attestation_object: Option<Vec<u8>>,
|
||||
|
||||
/// The CredentialId bytes extracted from the Authenticator Data.
|
||||
/// Used by Edge to return to the RP.
|
||||
pub credential_id: Option<Vec<u8>>,
|
||||
|
||||
//
|
||||
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2
|
||||
//
|
||||
/// Since VERSION 2
|
||||
pub extensions: Option<Vec<WebAuthnExtensionMakeCredentialOutput>>,
|
||||
|
||||
//
|
||||
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3
|
||||
//
|
||||
/// One of the WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to
|
||||
/// the transport that was used.
|
||||
pub used_transport: CtapTransport,
|
||||
|
||||
//
|
||||
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4
|
||||
//
|
||||
pub ep_att: bool,
|
||||
pub large_blob_supported: bool,
|
||||
pub resident_key: bool,
|
||||
|
||||
//
|
||||
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5
|
||||
//
|
||||
pub prf_enabled: bool,
|
||||
|
||||
//
|
||||
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6
|
||||
//
|
||||
pub unsigned_extension_outputs: Option<Vec<u8>>,
|
||||
|
||||
//
|
||||
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_7
|
||||
//
|
||||
pub hmac_secret: Option<HmacSecretSalt>,
|
||||
|
||||
/// ThirdPartyPayment Credential or not.
|
||||
pub third_party_payment: bool,
|
||||
|
||||
//
|
||||
// Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_8
|
||||
//
|
||||
/// Multiple WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to
|
||||
/// the transports that are supported.
|
||||
pub transports: Option<Vec<CtapTransport>>,
|
||||
|
||||
/// UTF-8 encoded JSON serialization of the client data.
|
||||
pub client_data_json: Option<Vec<u8>>,
|
||||
|
||||
/// UTF-8 encoded JSON serialization of the RegistrationResponse.
|
||||
pub registration_response_json: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl PluginMakeCredentialResponse {
|
||||
pub fn to_ctap_response(self) -> Result<Vec<u8>, WinWebAuthnError> {
|
||||
let attestation = self.try_into()?;
|
||||
let mut response_len = 0;
|
||||
let mut response_ptr = std::ptr::null_mut();
|
||||
webauthn_encode_make_credential_response(
|
||||
&attestation,
|
||||
&mut response_len,
|
||||
&mut response_ptr,
|
||||
)?
|
||||
.ok()
|
||||
.map_err(|err| {
|
||||
WinWebAuthnError::with_cause(
|
||||
ErrorKind::WindowsInternal,
|
||||
"WebAuthNEncodeMakeCredentialResponse() failed",
|
||||
err,
|
||||
)
|
||||
})?;
|
||||
|
||||
if response_ptr.is_null() {
|
||||
return Err(WinWebAuthnError::new(
|
||||
ErrorKind::WindowsInternal,
|
||||
"Received null pointer from WebAuthNEncodeMakeCredentialResponse",
|
||||
));
|
||||
}
|
||||
let response = unsafe {
|
||||
Vec::from_raw_parts(response_ptr, response_len as usize, response_len as usize)
|
||||
};
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PluginMakeCredentialResponse> for WEBAUTHN_CREDENTIAL_ATTESTATION {
|
||||
type Error = WinWebAuthnError;
|
||||
|
||||
fn try_from(value: PluginMakeCredentialResponse) -> Result<Self, Self::Error> {
|
||||
// Convert format type to UTF-16
|
||||
let format_type_utf16 = value.format_type.to_utf16();
|
||||
let pwszFormatType = format_type_utf16.as_ptr();
|
||||
std::mem::forget(format_type_utf16);
|
||||
|
||||
// Get authenticator data pointer and length
|
||||
let pbAuthenticatorData = value.authenticator_data.as_ptr();
|
||||
let cbAuthenticatorData = value.authenticator_data.len() as u32;
|
||||
std::mem::forget(value.authenticator_data);
|
||||
|
||||
// Get optional attestation statement pointer and length
|
||||
let (pbAttestation, cbAttestation) = match value.attestation_statement.as_ref() {
|
||||
Some(data) => (data.as_ptr(), data.len() as u32),
|
||||
None => (std::ptr::null(), 0),
|
||||
};
|
||||
std::mem::forget(value.attestation_statement);
|
||||
|
||||
// Get optional attestation object pointer and length
|
||||
let (pbAttestationObject, cbAttestationObject) = match value.attestation_object.as_ref() {
|
||||
Some(data) => (data.as_ptr(), data.len() as u32),
|
||||
None => (std::ptr::null(), 0),
|
||||
};
|
||||
std::mem::forget(value.attestation_object);
|
||||
|
||||
// Get optional credential ID pointer and length
|
||||
let (pbCredentialId, cbCredentialId) = match value.credential_id.as_ref() {
|
||||
Some(data) => (data.as_ptr(), data.len() as u32),
|
||||
None => (std::ptr::null(), 0),
|
||||
};
|
||||
std::mem::forget(value.credential_id);
|
||||
|
||||
// Convert extensions (TODO: implement proper extension conversion)
|
||||
let extensions = WEBAUTHN_EXTENSIONS {
|
||||
cExtensions: 0,
|
||||
pExtensions: std::ptr::null(),
|
||||
};
|
||||
|
||||
// Convert used transport enum to bitmask
|
||||
let dwUsedTransport = value.used_transport as u32;
|
||||
|
||||
// Get optional unsigned extension outputs pointer and length
|
||||
let (pbUnsignedExtensionOutputs, cbUnsignedExtensionOutputs) =
|
||||
match value.unsigned_extension_outputs.as_ref() {
|
||||
Some(data) => (data.as_ptr(), data.len() as u32),
|
||||
None => (std::ptr::null(), 0),
|
||||
};
|
||||
std::mem::forget(value.unsigned_extension_outputs);
|
||||
|
||||
// Convert optional HMAC secret (TODO: implement proper conversion)
|
||||
let pHmacSecret = std::ptr::null();
|
||||
|
||||
// Convert optional transports to bitmask
|
||||
let dwTransports = value
|
||||
.transports
|
||||
.as_ref()
|
||||
.map_or(0, |t| t.iter().map(|transport| *transport as u32).sum());
|
||||
|
||||
// Get optional client data JSON pointer and length
|
||||
let (pbClientDataJSON, cbClientDataJSON) = match value.client_data_json.as_ref() {
|
||||
Some(data) => (data.as_ptr(), data.len() as u32),
|
||||
None => (std::ptr::null(), 0),
|
||||
};
|
||||
std::mem::forget(value.client_data_json);
|
||||
|
||||
// Get optional registration response JSON pointer and length
|
||||
let (pbRegistrationResponseJSON, cbRegistrationResponseJSON) =
|
||||
match value.registration_response_json.as_ref() {
|
||||
Some(data) => (data.as_ptr(), data.len() as u32),
|
||||
None => (std::ptr::null(), 0),
|
||||
};
|
||||
std::mem::forget(value.registration_response_json);
|
||||
|
||||
let attestation = WEBAUTHN_CREDENTIAL_ATTESTATION {
|
||||
// Use version 8 to include all fields
|
||||
dwVersion: 8,
|
||||
pwszFormatType,
|
||||
cbAuthenticatorData,
|
||||
pbAuthenticatorData,
|
||||
cbAttestation,
|
||||
pbAttestation,
|
||||
// TODO: Support decode type. Just using WEBAUTHN_ATTESTATION_DECODE_NONE (0) for now.
|
||||
dwAttestationDecodeType: 0,
|
||||
pvAttestationDecode: std::ptr::null(),
|
||||
cbAttestationObject,
|
||||
pbAttestationObject,
|
||||
cbCredentialId,
|
||||
pbCredentialId,
|
||||
Extensions: extensions,
|
||||
dwUsedTransport,
|
||||
bEpAtt: value.ep_att,
|
||||
bLargeBlobSupported: value.large_blob_supported,
|
||||
bResidentKey: value.resident_key,
|
||||
bPrfEnabled: value.prf_enabled,
|
||||
cbUnsignedExtensionOutputs,
|
||||
pbUnsignedExtensionOutputs,
|
||||
pHmacSecret,
|
||||
bThirdPartyPayment: value.third_party_payment,
|
||||
dwTransports,
|
||||
cbClientDataJSON,
|
||||
pbClientDataJSON,
|
||||
cbRegistrationResponseJSON,
|
||||
pbRegistrationResponseJSON,
|
||||
};
|
||||
Ok(attestation)
|
||||
}
|
||||
}
|
||||
|
||||
webauthn_call!("WebAuthNEncodeMakeCredentialResponse" as fn webauthn_encode_make_credential_response(
|
||||
cbEncoded: *const WEBAUTHN_CREDENTIAL_ATTESTATION,
|
||||
pbEncoded: *mut u32,
|
||||
response_bytes: *mut *mut u8
|
||||
) -> HRESULT);
|
||||
|
||||
// GetAssertion types
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST {
|
||||
pub dwVersion: u32,
|
||||
pub pwszRpId: *const u16, // PCWSTR
|
||||
pub cbRpId: u32,
|
||||
pub pbRpId: *const u8,
|
||||
pub cbClientDataHash: u32,
|
||||
pub pbClientDataHash: *const u8,
|
||||
pub CredentialList: WEBAUTHN_CREDENTIAL_LIST,
|
||||
pub cbCborExtensionsMap: u32,
|
||||
pub pbCborExtensionsMap: *const u8,
|
||||
pub pAuthenticatorOptions: *const WebAuthnCtapCborAuthenticatorOptions,
|
||||
// Add other fields as needed...
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PluginGetAssertionRequest {
|
||||
inner: *const WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST,
|
||||
pub window_handle: HWND,
|
||||
pub transaction_id: GUID,
|
||||
pub request_signature: Vec<u8>,
|
||||
}
|
||||
|
||||
impl PluginGetAssertionRequest {
|
||||
pub fn rp_id(&self) -> &str {
|
||||
unsafe {
|
||||
let request = &*self.inner;
|
||||
let slice = std::slice::from_raw_parts(request.pbRpId, request.cbRpId as usize);
|
||||
str::from_utf8_unchecked(slice)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn client_data_hash(&self) -> &[u8] {
|
||||
let inner = self.as_ref();
|
||||
// SAFETY: Verified by Windows
|
||||
unsafe {
|
||||
std::slice::from_raw_parts(inner.pbClientDataHash, inner.cbClientDataHash as usize)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allow_credentials(&self) -> CredentialList {
|
||||
self.as_ref().CredentialList
|
||||
}
|
||||
|
||||
// TODO: Support extensions
|
||||
// pub fn extensions(&self) -> Options<Extensions> {}
|
||||
|
||||
pub fn authenticator_options(&self) -> Option<WebAuthnCtapCborAuthenticatorOptions> {
|
||||
let ptr = self.as_ref().pAuthenticatorOptions;
|
||||
if ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
unsafe { Some(*ptr) }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST> for PluginGetAssertionRequest {
|
||||
fn as_ref(&self) -> &WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST {
|
||||
unsafe { &*self.inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PluginGetAssertionRequest {
|
||||
fn drop(&mut self) {
|
||||
if !self.inner.is_null() {
|
||||
// leak memory if we cannot find the free function
|
||||
_ = webauthn_free_decoded_get_assertion_request(
|
||||
self.inner as *mut WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<NonNull<WEBAUTHN_PLUGIN_OPERATION_REQUEST>> for PluginGetAssertionRequest {
|
||||
type Error = WinWebAuthnError;
|
||||
|
||||
fn try_from(value: NonNull<WEBAUTHN_PLUGIN_OPERATION_REQUEST>) -> Result<Self, Self::Error> {
|
||||
unsafe {
|
||||
let request = value.as_ref();
|
||||
if !matches!(request.requestType, WebAuthnPluginRequestType::CTAP2_CBOR) {
|
||||
return Err(WinWebAuthnError::new(
|
||||
ErrorKind::Serialization,
|
||||
"Unknown plugin operation request type",
|
||||
));
|
||||
}
|
||||
let mut assertion_request: *mut WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST =
|
||||
std::ptr::null_mut();
|
||||
webauthn_decode_get_assertion_request(
|
||||
request.cbEncodedRequest,
|
||||
request.pbEncodedRequest,
|
||||
&mut assertion_request,
|
||||
)?
|
||||
.ok()
|
||||
.map_err(|err| {
|
||||
WinWebAuthnError::with_cause(
|
||||
ErrorKind::WindowsInternal,
|
||||
"Failed to decode get assertion request",
|
||||
err,
|
||||
)
|
||||
})?;
|
||||
Ok(Self {
|
||||
inner: assertion_request as *const WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST,
|
||||
window_handle: request.hWnd,
|
||||
transaction_id: request.transactionId,
|
||||
request_signature: Vec::from_raw_parts(
|
||||
request.pbRequestSignature,
|
||||
request.cbEncodedRequest as usize,
|
||||
request.cbEncodedRequest as usize,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
// Windows API function signatures for decoding get assertion requests
|
||||
webauthn_call!("WebAuthNDecodeGetAssertionRequest" as fn webauthn_decode_get_assertion_request(
|
||||
cbEncoded: u32,
|
||||
pbEncoded: *const u8,
|
||||
ppGetAssertionRequest: *mut *mut WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST
|
||||
) -> HRESULT);
|
||||
|
||||
webauthn_call!("WebAuthNFreeDecodedGetAssertionRequest" as fn webauthn_free_decoded_get_assertion_request(
|
||||
pGetAssertionRequest: *mut WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST
|
||||
) -> ());
|
||||
|
||||
// CancelOperation Types
|
||||
pub(super) struct WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST {
|
||||
transactionId: GUID,
|
||||
cbRequestSignature: u32,
|
||||
pbRequestSignature: *const u8,
|
||||
}
|
||||
|
||||
pub struct PluginCancelOperationRequest {
|
||||
inner: NonNull<WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST>,
|
||||
}
|
||||
|
||||
impl PluginCancelOperationRequest {
|
||||
/// Request transaction ID
|
||||
fn transaction_id(&self) -> GUID {
|
||||
self.as_ref().transactionId
|
||||
}
|
||||
|
||||
/// Request signature.
|
||||
fn request_signature(&self) -> &[u8] {
|
||||
unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
self.as_ref().pbRequestSignature,
|
||||
self.as_ref().cbRequestSignature as usize,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST> for PluginCancelOperationRequest {
|
||||
fn as_ref(&self) -> &WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST {
|
||||
// SAFETY: Pointer is received from Windows so we assume it is correct.
|
||||
unsafe { self.inner.as_ref() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NonNull<WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST>> for PluginCancelOperationRequest {
|
||||
fn from(value: NonNull<WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST>) -> Self {
|
||||
Self { inner: value }
|
||||
}
|
||||
}
|
||||
|
||||
/// Plugin lock status enum as defined in the IDL
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum PluginLockStatus {
|
||||
PluginLocked = 0,
|
||||
PluginUnlocked = 1,
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,39 @@ use windows::{
|
||||
},
|
||||
};
|
||||
|
||||
use crate::win_webauthn::{com::ComBuffer, ErrorKind, WinWebAuthnError};
|
||||
use crate::win_webauthn::{
|
||||
// com::ComBuffer,
|
||||
ErrorKind,
|
||||
WinWebAuthnError,
|
||||
};
|
||||
|
||||
macro_rules! webauthn_call {
|
||||
($symbol:literal as fn $fn_name:ident($($arg:ident: $arg_type:ty),+) -> $result_type:ty) => (
|
||||
pub(super) fn $fn_name($($arg: $arg_type),*) -> Result<$result_type, WinWebAuthnError> {
|
||||
let library = crate::win_webauthn::util::load_webauthn_lib()?;
|
||||
let response = unsafe {
|
||||
let address = GetProcAddress(library, s!($symbol)).ok_or(
|
||||
WinWebAuthnError::new(
|
||||
ErrorKind::DllLoad,
|
||||
&format!(
|
||||
"Failed to load function {}",
|
||||
$symbol
|
||||
),
|
||||
),
|
||||
)?;
|
||||
|
||||
let function: unsafe extern "cdecl" fn(
|
||||
$($arg: $arg_type),*
|
||||
) -> $result_type = std::mem::transmute_copy(&address);
|
||||
function($($arg),*)
|
||||
};
|
||||
crate::win_webauthn::util::free_webauthn_lib(library)?;
|
||||
Ok(response)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) use webauthn_call;
|
||||
|
||||
pub(super) fn load_webauthn_lib() -> Result<HMODULE, WinWebAuthnError> {
|
||||
unsafe {
|
||||
@@ -49,7 +81,7 @@ pub(super) trait WindowsString {
|
||||
fn to_utf16(&self) -> Vec<u16>;
|
||||
|
||||
// Copies a string to a buffer from the OLE allocator
|
||||
fn to_com_utf16(&self) -> (*mut u16, u32);
|
||||
// fn to_com_utf16(&self) -> (*mut u16, u32);
|
||||
}
|
||||
|
||||
impl WindowsString for str {
|
||||
@@ -58,6 +90,7 @@ impl WindowsString for str {
|
||||
self.encode_utf16().chain(std::iter::once(0)).collect()
|
||||
}
|
||||
|
||||
/*
|
||||
fn to_com_utf16(&self) -> (*mut u16, u32) {
|
||||
let wide_bytes: Vec<u8> = self
|
||||
.to_utf16()
|
||||
@@ -67,6 +100,7 @@ impl WindowsString for str {
|
||||
let (ptr, byte_count) = ComBuffer::from_buffer(&wide_bytes);
|
||||
(ptr as *mut u16, byte_count)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
pub struct ArrayPointerIterator<'a, T> {
|
||||
|
||||
Reference in New Issue
Block a user