diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs index 527f579e5c2..f6b65ddca77 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/assert.rs @@ -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( diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs index 7d2a023d869..a0b629a5533 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs @@ -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, }, }; diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/make_credential.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/make_credential.rs index 671e7c8978b..a370c5f8db4 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/make_credential.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/make_credential.rs @@ -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( diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/mod.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/mod.rs index 87c76dc3b8e..21c22bba9f9 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/mod.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/mod.rs @@ -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 { - // 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(&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 { - 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>> = options - .supported_rp_ids - .map(|ids| ids.iter().map(|id| id.to_utf16()).collect()); - let supported_rp_id_ptrs: Option> = 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"); - } -} diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/com.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/plugin/com.rs similarity index 88% rename from apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/com.rs rename to apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/plugin/com.rs index c96427815ec..9bb1ff85ab7 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/com.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/plugin/com.rs @@ -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> = 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, Box>; - - /// 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, Box>; - - /// Cancel an ongoing operation. - fn cancel_operation(&self, request: PluginCancelOperationRequest) - -> Result<(), Box>; - - /// Retrieve lock status. - fn lock_status(&self) -> Result>; -} - // IPluginAuthenticator interface #[interface("d26bcf6f-b54c-43ff-9f06-d5bf148625f7")] pub unsafe trait IPluginAuthenticator: windows::core::IUnknown { diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/plugin/mod.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/plugin/mod.rs new file mode 100644 index 00000000000..689c8c3e997 --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/plugin/mod.rs @@ -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 { + // 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(&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 { + 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>> = options + .supported_rp_ids + .map(|ids| ids.iter().map(|id| id.to_utf16()).collect()); + let supported_rp_id_ptrs: Option> = 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, Box>; + + /// 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, Box>; + + /// Cancel an ongoing operation. + fn cancel_operation(&self, request: PluginCancelOperationRequest) + -> Result<(), Box>; + + /// Retrieve lock status. + fn lock_status(&self) -> Result>; +} + +#[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"); + } +} diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/plugin/types.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/plugin/types.rs new file mode 100644 index 00000000000..84a61473b41 --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/plugin/types.rs @@ -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 { + Self::to_optional_bool(self.lUp) + } + + pub fn user_verification(&self) -> Option { + Self::to_optional_bool(self.lUv) + } + + pub fn require_resident_key(&self) -> Option { + Self::to_optional_bool(self.lRequireResidentKey) + } + + fn to_optional_bool(value: i32) -> Option { + 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, + + /// Plugin Authenticator Logo for the Light themes. + /// + /// String should contain a valid SVG 1.1 document. + pub light_theme_logo_svg: Option, + + // 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, + + /// 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>, +} + +impl PluginAddAuthenticatorOptions { + pub fn light_theme_logo_b64(&self) -> Option> { + self.light_theme_logo_svg + .as_ref() + .map(|svg| Self::encode_svg(&svg)) + } + + pub fn dark_theme_logo_b64(&self) -> Option> { + self.dark_theme_logo_svg + .as_ref() + .map(|svg| Self::encode_svg(&svg)) + } + + fn encode_svg(svg: &str) -> Vec { + 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, +} + +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> for PluginAddAuthenticatorResponse { + fn from(value: NonNull) -> 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, +} + +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 { + let ptr = self.as_ref().pAuthenticatorOptions; + if ptr.is_null() { + return None; + } + unsafe { Some(*ptr) } + } +} + +impl AsRef 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> for PluginMakeCredentialRequest { + type Error = WinWebAuthnError; + + fn try_from(value: NonNull) -> Result { + 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, + + ///Encoded CBOR attestation information + pub attestation_statement: Option>, + + // 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>, + + /// The CredentialId bytes extracted from the Authenticator Data. + /// Used by Edge to return to the RP. + pub credential_id: Option>, + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2 + // + /// Since VERSION 2 + pub extensions: Option>, + + // + // 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>, + + // + // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_7 + // + pub hmac_secret: Option, + + /// 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>, + + /// UTF-8 encoded JSON serialization of the client data. + pub client_data_json: Option>, + + /// UTF-8 encoded JSON serialization of the RegistrationResponse. + pub registration_response_json: Option>, +} + +impl PluginMakeCredentialResponse { + pub fn to_ctap_response(self) -> Result, 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 for WEBAUTHN_CREDENTIAL_ATTESTATION { + type Error = WinWebAuthnError; + + fn try_from(value: PluginMakeCredentialResponse) -> Result { + // 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, +} + +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 {} + + pub fn authenticator_options(&self) -> Option { + let ptr = self.as_ref().pAuthenticatorOptions; + if ptr.is_null() { + return None; + } + unsafe { Some(*ptr) } + } +} + +impl AsRef 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> for PluginGetAssertionRequest { + type Error = WinWebAuthnError; + + fn try_from(value: NonNull) -> Result { + 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, +} + +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 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> for PluginCancelOperationRequest { + fn from(value: NonNull) -> 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, +} diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/types/mod.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/types/mod.rs index c2219f3bdf1..1dbc4be1a19 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/types/mod.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/types/mod.rs @@ -1,5 +1,4 @@ //! Types and functions defined in the Windows WebAuthn API. - use std::{collections::HashSet, fmt::Display, mem::MaybeUninit, ptr::NonNull}; use base64::{engine::general_purpose::STANDARD, Engine as _}; @@ -14,185 +13,12 @@ use windows::{ use windows_core::{s, PCWSTR}; use crate::win_webauthn::{ - com::ComBuffer, + // com::ComBuffer, util::{ArrayPointerIterator, WindowsString}, - Clsid, ErrorKind, WinWebAuthnError, + 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 = super::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),*) - }; - super::util::free_webauthn_lib(library)?; - Ok(response) - } - ) -} - -/// 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(super) 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, - - /// Plugin Authenticator Logo for the Light themes. - /// - /// String should contain a valid SVG 1.1 document. - pub light_theme_logo_svg: Option, - - // 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, - - /// 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>, -} - -impl PluginAddAuthenticatorOptions { - pub fn light_theme_logo_b64(&self) -> Option> { - self.light_theme_logo_svg - .as_ref() - .map(|svg| Self::encode_svg(&svg)) - } - - pub fn dark_theme_logo_b64(&self) -> Option> { - self.dark_theme_logo_svg - .as_ref() - .map(|svg| Self::encode_svg(&svg)) - } - - fn encode_svg(svg: &str) -> Vec { - 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, -} - -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> for PluginAddAuthenticatorResponse { - fn from(value: NonNull) -> 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 -) -> ()); - /// List of its supported protocol versions and extensions, its AAGUID, and /// other aspects of its overall capabilities. pub struct AuthenticatorInfo { @@ -348,42 +174,6 @@ impl From<&CtapVersion> for String { } } -/// 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(super) 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, -} - #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct WEBAUTHN_RP_ENTITY_INFORMATION { @@ -533,243 +323,84 @@ impl WEBAUTHN_COSE_CREDENTIAL_PARAMETERS { #[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, -} - -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 { - let ptr = self.as_ref().pAuthenticatorOptions; - if ptr.is_null() { - return None; - } - unsafe { Some(*ptr) } - } -} - -impl AsRef 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> for PluginMakeCredentialRequest { - type Error = WinWebAuthnError; - - fn try_from(value: NonNull) -> Result { - 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 {} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -struct WEBAUTHN_CREDENTIAL_ATTESTATION { +pub(crate) struct WEBAUTHN_CREDENTIAL_ATTESTATION { /// Version of this structure, to allow for modifications in the future. - dwVersion: u32, + pub(crate) dwVersion: u32, /// Attestation format type - pwszFormatType: *const u16, // PCWSTR + pub(crate) pwszFormatType: *const u16, // PCWSTR /// Size of cbAuthenticatorData. - cbAuthenticatorData: u32, + pub(crate) cbAuthenticatorData: u32, /// Authenticator data that was created for this credential. //_Field_size_bytes_(cbAuthenticatorData) - pbAuthenticatorData: *const u8, + pub(crate) pbAuthenticatorData: *const u8, /// Size of CBOR encoded attestation information /// 0 => encoded as CBOR null value. - cbAttestation: u32, + pub(crate) cbAttestation: u32, ///Encoded CBOR attestation information // _Field_size_bytes_(cbAttestation) - pbAttestation: *const u8, + pub(crate) pbAttestation: *const u8, - dwAttestationDecodeType: u32, + pub(crate) 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; - pvAttestationDecode: *const u8, + pub(crate) pvAttestationDecode: *const u8, /// The CBOR encoded Attestation Object to be returned to the RP. - cbAttestationObject: u32, + pub(crate) cbAttestationObject: u32, // _Field_size_bytes_(cbAttestationObject) - pbAttestationObject: *const u8, + pub(crate) pbAttestationObject: *const u8, /// The CredentialId bytes extracted from the Authenticator Data. /// Used by Edge to return to the RP. - cbCredentialId: u32, + pub(crate) cbCredentialId: u32, // _Field_size_bytes_(cbCredentialId) - pbCredentialId: *const u8, + pub(crate) pbCredentialId: *const u8, // // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2 // /// Since VERSION 2 - Extensions: WEBAUTHN_EXTENSIONS, + pub(crate) Extensions: WEBAUTHN_EXTENSIONS, // // 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. - dwUsedTransport: u32, + pub(crate) dwUsedTransport: u32, // // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4 // - bEpAtt: bool, - bLargeBlobSupported: bool, - bResidentKey: bool, + pub(crate) bEpAtt: bool, + pub(crate) bLargeBlobSupported: bool, + pub(crate) bResidentKey: bool, // // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5 // - bPrfEnabled: bool, + pub(crate) bPrfEnabled: bool, // // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6 // - cbUnsignedExtensionOutputs: u32, + pub(crate) cbUnsignedExtensionOutputs: u32, // _Field_size_bytes_(cbUnsignedExtensionOutputs) - pbUnsignedExtensionOutputs: *const u8, + pub(crate) pbUnsignedExtensionOutputs: *const u8, // // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_7 // - pHmacSecret: *const WEBAUTHN_HMAC_SECRET_SALT, + pub(crate) pHmacSecret: *const WEBAUTHN_HMAC_SECRET_SALT, // ThirdPartyPayment Credential or not. - bThirdPartyPayment: bool, + pub(crate) bThirdPartyPayment: bool, // // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_8 @@ -777,17 +408,17 @@ struct WEBAUTHN_CREDENTIAL_ATTESTATION { // Multiple WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to // the transports that are supported. - dwTransports: u32, + pub(crate) dwTransports: u32, // UTF-8 encoded JSON serialization of the client data. - cbClientDataJSON: u32, + pub(crate) cbClientDataJSON: u32, // _Field_size_bytes_(cbClientDataJSON) - pbClientDataJSON: *const u8, + pub(crate) pbClientDataJSON: *const u8, // UTF-8 encoded JSON serialization of the RegistrationResponse. - cbRegistrationResponseJSON: u32, + pub(crate) cbRegistrationResponseJSON: u32, // _Field_size_bytes_(cbRegistrationResponseJSON) - pbRegistrationResponseJSON: *const u8, + pub(crate) pbRegistrationResponseJSON: *const u8, } pub enum AttestationFormat { @@ -819,7 +450,7 @@ pub enum AttestationDecodeType { Common(), } -struct WEBAUTHN_HMAC_SECRET_SALT { +pub(crate) struct WEBAUTHN_HMAC_SECRET_SALT { /// Size of pbFirst. cbFirst: u32, // _Field_size_bytes_(cbFirst) @@ -839,7 +470,7 @@ pub struct HmacSecretSalt { #[repr(C)] #[derive(Debug, Copy, Clone)] -struct WEBAUTHN_EXTENSION { +pub(crate) struct WEBAUTHN_EXTENSION { pwszExtensionIdentifier: *const u16, cbExtension: u32, pvExtension: *mut u8, @@ -861,413 +492,12 @@ pub enum WebAuthnExtensionMakeCredentialOutput { #[repr(C)] #[derive(Debug, Copy, Clone)] -struct WEBAUTHN_EXTENSIONS { - cExtensions: u32, +pub(crate) struct WEBAUTHN_EXTENSIONS { + pub(crate) cExtensions: u32, // _Field_size_(cExtensions) - pExtensions: *const WEBAUTHN_EXTENSION, + pub(crate) pExtensions: *const WEBAUTHN_EXTENSION, } -pub struct PluginMakeCredentialResponse { - /// Attestation format type - pub format_type: String, // PCWSTR - - /// Authenticator data that was created for this credential. - pub authenticator_data: Vec, - - ///Encoded CBOR attestation information - pub attestation_statement: Option>, - - // 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>, - - /// The CredentialId bytes extracted from the Authenticator Data. - /// Used by Edge to return to the RP. - pub credential_id: Option>, - - // - // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2 - // - /// Since VERSION 2 - pub extensions: Option>, - - // - // 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>, - - // - // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_7 - // - pub hmac_secret: Option, - - /// 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>, - - /// UTF-8 encoded JSON serialization of the client data. - pub client_data_json: Option>, - - /// UTF-8 encoded JSON serialization of the RegistrationResponse. - pub registration_response_json: Option>, -} - -impl PluginMakeCredentialResponse { - pub fn to_ctap_response(mut self) -> Result, WinWebAuthnError> { - let attestation = self.try_into()?; - let mut response_len = 0; - let mut response_ptr = std::ptr::null_mut(); - let result = 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 for WEBAUTHN_CREDENTIAL_ATTESTATION { - type Error = WinWebAuthnError; - - fn try_from(value: PluginMakeCredentialResponse) -> Result { - // 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); - -#[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, -} - -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 {} - - pub fn authenticator_options(&self) -> Option { - let ptr = self.as_ref().pAuthenticatorOptions; - if ptr.is_null() { - return None; - } - unsafe { Some(*ptr) } - } -} - -impl AsRef 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> for PluginGetAssertionRequest { - type Error = WinWebAuthnError; - - fn try_from(value: NonNull) -> Result { - 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 -) -> ()); - -// pub struct PluginGetAssertionResponse {} - -pub(super) struct WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST { - transactionId: GUID, - cbRequestSignature: u32, - pbRequestSignature: *const u8, -} - -pub struct PluginCancelOperationRequest { - inner: NonNull, -} - -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 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> for PluginCancelOperationRequest { - fn from(value: NonNull) -> 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, -} struct CredentialId(Vec); impl TryFrom> for CredentialId { @@ -1362,6 +592,7 @@ pub struct CredentialEx { } impl CredentialEx { + /* fn new_for_com( version: u32, id: &CredentialId, @@ -1387,6 +618,7 @@ impl CredentialEx { }; Self { inner: ptr } } + */ } impl AsRef for CredentialEx { @@ -1413,46 +645,6 @@ pub enum CtapTransport { Hybrid = 0x20, SmartCard = 0x40, } -/// 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 { - Self::to_optional_bool(self.lUp) - } - - pub fn user_verification(&self) -> Option { - Self::to_optional_bool(self.lUv) - } - - pub fn require_resident_key(&self) -> Option { - Self::to_optional_bool(self.lRequireResidentKey) - } - - fn to_optional_bool(value: i32) -> Option { - match value { - x if x > 0 => Some(true), - x if x < 0 => Some(false), - _ => None, - } - } -} -type WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS = WebAuthnCtapCborAuthenticatorOptions; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -1461,7 +653,7 @@ pub struct CredentialList { pub ppCredentials: *const *const WEBAUTHN_CREDENTIAL_EX, } -type WEBAUTHN_CREDENTIAL_LIST = CredentialList; +pub(crate) type WEBAUTHN_CREDENTIAL_LIST = CredentialList; pub struct CredentialListIterator<'a> { inner: ArrayPointerIterator<'a, *const WEBAUTHN_CREDENTIAL_EX>, } diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/util.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/util.rs index c8d6454c96b..d2e163ab26c 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/util.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/win_webauthn/util.rs @@ -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 { unsafe { @@ -49,7 +81,7 @@ pub(super) trait WindowsString { fn to_utf16(&self) -> Vec; // 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 = 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> {