From 13835d683c26570a91f55b064795803cd4015db4 Mon Sep 17 00:00:00 2001 From: Michael Farrell Date: Sat, 30 Dec 2023 20:34:15 +1000 Subject: [PATCH] wire up more of the request, make test mode work --- .../examples/authenticate.rs | 47 ++++++++-- .../src/win10/rdp/message.rs | 62 ++++++++----- .../src/win10/rdp/mod.rs | 90 ++++++++++++++++--- 3 files changed, 157 insertions(+), 42 deletions(-) diff --git a/webauthn-authenticator-rs/examples/authenticate.rs b/webauthn-authenticator-rs/examples/authenticate.rs index 17aec86a..39aba391 100644 --- a/webauthn-authenticator-rs/examples/authenticate.rs +++ b/webauthn-authenticator-rs/examples/authenticate.rs @@ -7,7 +7,7 @@ use std::io::{stdin, stdout, Write}; use std::time::Duration; use clap::clap_derive::ValueEnum; -#[cfg(any(feature = "cable", feature = "softtoken"))] +#[cfg(any(feature = "cable", feature = "softtoken", feature = "win10-rdp"))] use clap::Args; use clap::{Parser, Subcommand}; #[cfg(feature = "cable")] @@ -29,7 +29,7 @@ use webauthn_authenticator_rs::ui::{Cli, UiCallback}; use webauthn_authenticator_rs::AuthenticatorBackend; use webauthn_rs_core::proto::RequestAuthenticationExtensions; use webauthn_rs_core::WebauthnCore as Webauthn; -use webauthn_rs_proto::{AttestationConveyancePreference, COSEAlgorithm, UserVerificationPolicy}; +use webauthn_rs_proto::{AttestationConveyancePreference, COSEAlgorithm, UserVerificationPolicy, AuthenticatorAttachment}; #[derive(Debug, clap::Parser)] #[clap(about = "Register and authenticate test")] @@ -41,6 +41,10 @@ pub struct CliParser { /// User verification policy for the request. #[clap(short, long, value_enum, default_value_t)] verification_policy: UvPolicy, + + /// Authenticator attachment policy for the request. + #[clap(short, long, value_enum, default_value_t)] + attachment: Attachment, } #[derive(ValueEnum, Clone, Default, Debug)] @@ -61,6 +65,24 @@ impl From for UserVerificationPolicy { } } +#[derive(ValueEnum, Clone, Default, Debug)] +pub enum Attachment { + #[default] + Any, + Platform, + CrossPlatform, +} + +impl From for Option { + fn from(value: Attachment) -> Self { + match value { + Attachment::Any => None, + Attachment::Platform => Some(AuthenticatorAttachment::Platform), + Attachment::CrossPlatform => Some(AuthenticatorAttachment::CrossPlatform), + } + } +} + #[cfg(feature = "ctap2")] async fn select_transport(ui: &U) -> impl AuthenticatorBackend + '_ { use futures::StreamExt; @@ -134,6 +156,15 @@ impl CableOpt { } } + +#[cfg(feature = "win10-rdp")] +#[derive(Debug, Args, Clone)] +pub struct Win10RdpOpt { + #[clap(long)] + pub test_mode: bool, +} + + #[derive(Debug, Clone, Subcommand)] enum Provider { #[cfg(feature = "softtoken")] @@ -163,7 +194,7 @@ enum Provider { #[cfg(feature = "win10-rdp")] /// Windows 10 WebAuthn API, via an RDP Virtual Channel. - Win10Rdp, + Win10Rdp(Win10RdpOpt), } impl Provider { @@ -215,8 +246,12 @@ impl Provider { #[cfg(feature = "win10")] Provider::Win10 => Box::::default(), #[cfg(feature = "win10-rdp")] - Provider::Win10Rdp => { - Box::new(webauthn_authenticator_rs::win10::rdp::Win10Rdp::new().unwrap()) + Provider::Win10Rdp(o) => { + let mut rdp = webauthn_authenticator_rs::win10::rdp::Win10Rdp::new().unwrap(); + if o.test_mode { + rdp.enable_test_mode(); + } + Box::new(rdp) } } } @@ -266,7 +301,7 @@ async fn main() { None, COSEAlgorithm::secure_algs(), false, - None, + opt.attachment.into(), false, ) .unwrap(); diff --git a/webauthn-authenticator-rs/src/win10/rdp/message.rs b/webauthn-authenticator-rs/src/win10/rdp/message.rs index 4f5364d6..c0412cd3 100644 --- a/webauthn-authenticator-rs/src/win10/rdp/message.rs +++ b/webauthn-authenticator-rs/src/win10/rdp/message.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; use serde_bytes::{ByteArray, ByteBuf}; use uuid::Uuid; +use webauthn_rs_proto::AuthenticatorTransport; /// #[derive(Default, Deserialize, Serialize, Debug, Clone, PartialEq)] @@ -22,12 +23,12 @@ pub struct ChannelRequest { #[serde(rename_all = "camelCase")] pub struct WebauthnPara { pub wnd: isize, - pub attachment: u8, + pub attachment: u32, pub require_resident: bool, pub prefer_resident: bool, - pub user_verification: u8, - pub attestation_preference: u8, - pub enterprise_attestation: u8, + pub user_verification: u32, + pub attestation_preference: u32, + pub enterprise_attestation: u32, #[serde(with = "UuidDef")] pub cancellation_id: Uuid, } @@ -36,31 +37,44 @@ pub struct WebauthnPara { #[derive(Deserialize, Serialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct ChannelResponse { - // pub device_info: DeviceInfo, + pub device_info: Option, pub status: u8, pub response: Option, // TODO: deviceInfoList } -// /// -// #[derive(Deserialize, Serialize, Debug, Clone)] -// #[serde(rename_all = "camelCase")] -// pub struct DeviceInfo { -// max_msg_size: Option, -// max_serialized_large_blob_array: Option, -// provider_type: String, -// provider_name: String, -// device_path: Option, -// #[serde(rename = "Manufacturer")] -// manufacturer: Option, -// #[serde(rename = "Product")] -// product: Option, -// #[serde(rename = "aaGuid")] -// aaguid: Uuid, -// resident_key: Option, -// uv_status: Option, -// uv_retries: Option, -// } +/// +#[derive(Deserialize, Serialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct DeviceInfo { + max_msg_size: Option, + max_serialized_large_blob_array: Option, + provider_type: String, + provider_name: String, + device_path: Option, + manufacturer: Option, + product: Option, + #[serde(rename = "aaGuid")] + aaguid: Option, + resident_key: Option, + uv_status: Option, + uv_retries: Option, + u2f_protocol: Option, +} + +impl DeviceInfo { + pub fn get_transport(&self) -> Option { + let provider = self.provider_type.to_ascii_lowercase(); + match provider.as_str() { + "hid" => Some(AuthenticatorTransport::Usb), + "nfc" => Some(AuthenticatorTransport::Nfc), + "ble" => Some(AuthenticatorTransport::Ble), + "platform" => Some(AuthenticatorTransport::Internal), + "test" => Some(AuthenticatorTransport::Test), + _ => None, + } + } +} type UuidByteArray = ByteArray<{ std::mem::size_of::() }>; diff --git a/webauthn-authenticator-rs/src/win10/rdp/mod.rs b/webauthn-authenticator-rs/src/win10/rdp/mod.rs index 70ce89dc..30c7ca5b 100644 --- a/webauthn-authenticator-rs/src/win10/rdp/mod.rs +++ b/webauthn-authenticator-rs/src/win10/rdp/mod.rs @@ -18,13 +18,25 @@ use crate::{ use base64urlsafedata::Base64UrlSafeData; use uuid::Uuid; use webauthn_rs_proto::{ - AuthenticatorAttestationResponseRaw, RegisterPublicKeyCredential, - RegistrationExtensionsClientOutputs, + AttestationConveyancePreference, AuthenticatorAttachment, AuthenticatorAttestationResponseRaw, + RegisterPublicKeyCredential, RegistrationExtensionsClientOutputs, ResidentKeyRequirement, + UserVerificationPolicy, }; use windows::{ core::{AsImpl as _, Result as WinResult}, Win32::{ Foundation::{E_FAIL, NTE_BAD_LEN}, + Networking::WindowsWebServices::{ + WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ANY, + WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT, + WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT, + WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE, WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY, + WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM, + WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM, + WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED, + WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED, + WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED, + }, System::RemoteDesktop::{IWTSPlugin, IWTSVirtualChannelManager}, }, }; @@ -32,6 +44,7 @@ use windows::{ pub struct Win10Rdp { plugin: IWTSPlugin, iface: IWTSVirtualChannelManager, + test_mode: bool, } impl Win10Rdp { @@ -39,6 +52,7 @@ impl Win10Rdp { let o = Self { plugin: plugin::get_webauthn_iwtsplugin()?, iface: VirtualChannelManager::new().into(), + test_mode: false, }; unsafe { @@ -49,6 +63,10 @@ impl Win10Rdp { Ok(o) } + pub fn enable_test_mode(&mut self) { + self.test_mode = true; + } + fn connect(&self) -> WinResult { // Get back the IWTSListenerCallback let Some(c) = unsafe { self.iface.as_impl() }.get_webauthn_callback() else { @@ -114,19 +132,65 @@ impl AuthenticatorBackendHashedClientData for Win10Rdp { }; let window = Window::new()?; + + let flags = if self.test_mode { + warn!("using test mode!"); + 0x8800_0000 + } else { + 0x0004_0000 // CTAPCLT_DUAL_FLAG + | match authenticator_selection.user_verification { + UserVerificationPolicy::Discouraged_DO_NOT_USE => 0x0100_0000, + UserVerificationPolicy::Preferred => 0x0080_0000, + UserVerificationPolicy::Required => 0x0040_0000, + } + }; let webauthn_para = WebauthnPara { wnd: window.hwnd.0, - attachment: 0, - require_resident: false, - prefer_resident: false, - user_verification: 0, - attestation_preference: 0, + attachment: match authenticator_selection.authenticator_attachment { + None => WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY, + Some(AuthenticatorAttachment::CrossPlatform) => { + WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM + } + Some(AuthenticatorAttachment::Platform) => { + WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM + } + }, + require_resident: authenticator_selection.require_resident_key + || matches!( + authenticator_selection.resident_key, + Some(ResidentKeyRequirement::Required) + ), + prefer_resident: matches!( + authenticator_selection.resident_key, + Some(ResidentKeyRequirement::Preferred) + ), + user_verification: match authenticator_selection.user_verification { + UserVerificationPolicy::Required => WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED, + UserVerificationPolicy::Preferred => { + WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED + } + UserVerificationPolicy::Discouraged_DO_NOT_USE => { + WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED + } + }, + attestation_preference: match options.attestation { + None => WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ANY, + Some(AttestationConveyancePreference::None) => { + WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE + } + Some(AttestationConveyancePreference::Indirect) => { + WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT + } + Some(AttestationConveyancePreference::Direct) => { + WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT + } + }, enterprise_attestation: 0, cancellation_id: Uuid::nil(), }; let (channel_response, ret) = c - .transcieve_cbor(mc, 4194304, 60000, Uuid::nil(), webauthn_para) + .transcieve_cbor(mc, flags, 60000, Uuid::nil(), webauthn_para) .map_err(|_| WebauthnCError::Internal)?; drop(window); @@ -160,10 +224,12 @@ impl AuthenticatorBackendHashedClientData for Win10Rdp { response: AuthenticatorAttestationResponseRaw { attestation_object: Base64UrlSafeData(raw), client_data_json: Base64UrlSafeData(vec![]), - // All transports the token supports, as opposed to the - // transport which was actually used. - // TODO - transports: None, + // The transport actually used + transports: channel_response + .device_info + .as_ref() + .and_then(|device_info| device_info.get_transport()) + .map(|t| vec![t]), }, }) }