webauthn_authenticator_rs/win10/
mod.rs

1//! Bindings for Windows 10 WebAuthn API.
2//!
3//! This API is available in Windows 10 bulid 1903 and later.
4//!
5//! ## API docs
6//!
7//! * [MSDN: WebAuthn API](https://learn.microsoft.com/en-us/windows/win32/api/webauthn/)
8//! * [webauthn.h](github.com/microsoft/webauthn) (describes versions)
9//! * [windows-rs API](https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/Networking/WindowsWebServices/index.html)
10#[cfg(feature = "win10")]
11mod clientdata;
12#[cfg(feature = "win10")]
13mod cose;
14#[cfg(feature = "win10")]
15mod credential;
16#[cfg(feature = "win10")]
17mod extensions;
18#[cfg(feature = "win10")]
19mod gui;
20#[cfg(feature = "win10")]
21mod native;
22#[cfg(feature = "win10")]
23mod rp;
24#[cfg(feature = "win10")]
25mod user;
26
27#[cfg(feature = "win10")]
28use crate::win10::{
29    clientdata::WinClientData,
30    cose::WinCoseCredentialParameters,
31    credential::{native_to_transports, WinCredentialList},
32    extensions::{
33        native_to_assertion_extensions, native_to_registration_extensions,
34        WinExtensionMakeCredentialRequest, WinExtensionsRequest,
35    },
36    gui::Window,
37    native::{WinPtr, WinWrapper},
38    rp::WinRpEntityInformation,
39    user::WinUserEntityInformation,
40};
41use crate::{
42    error::WebauthnCError,
43    util::{creation_to_clientdata, get_to_clientdata},
44    AuthenticatorBackend, Url, BASE64_ENGINE,
45};
46
47use base64::Engine;
48use base64urlsafedata::Base64UrlSafeData;
49use webauthn_rs_proto::{
50    AuthenticatorAssertionResponseRaw, AuthenticatorAttachment,
51    AuthenticatorAttestationResponseRaw, PublicKeyCredential, PublicKeyCredentialCreationOptions,
52    PublicKeyCredentialRequestOptions, RegisterPublicKeyCredential, UserVerificationPolicy,
53};
54
55#[cfg(feature = "win10")]
56use windows::{
57    core::{HSTRING, PCWSTR},
58    Win32::{Foundation::BOOL, Networking::WindowsWebServices::*},
59};
60
61use std::slice::from_raw_parts;
62
63/// Authenticator backend for Windows 10 WebAuthn API.
64pub struct Win10 {}
65
66impl Default for Win10 {
67    fn default() -> Self {
68        unsafe {
69            trace!(
70                "WebAuthNGetApiVersionNumber(): {}",
71                WebAuthNGetApiVersionNumber()
72            );
73            match WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable() {
74                Ok(v) => trace!(
75                    "WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable() = {:?}",
76                    <_ as Into<bool>>::into(v)
77                ),
78                Err(e) => trace!("error requesting platform authenticator: {:?}", e),
79            }
80        }
81
82        Self {}
83    }
84}
85
86impl AuthenticatorBackend for Win10 {
87    /// Perform a registration action using Windows WebAuth API.
88    ///
89    /// This wraps [WebAuthNAuthenticatorMakeCredential].
90    ///
91    /// [WebAuthnAuthenticatorMakeCredential]: https://learn.microsoft.com/en-us/windows/win32/api/webauthn/nf-webauthn-webauthnauthenticatormakecredential
92    fn perform_register(
93        &mut self,
94        origin: Url,
95        options: PublicKeyCredentialCreationOptions,
96        timeout_ms: u32,
97    ) -> Result<RegisterPublicKeyCredential, WebauthnCError> {
98        let hwnd = Window::new()?;
99        // let hwnd = get_hwnd().ok_or(WebauthnCError::CannotFindHWND)?;
100        let rp = WinRpEntityInformation::new(options.rp)?;
101        let userinfo = WinUserEntityInformation::new(options.user)?;
102        let pubkeycredparams = WinCoseCredentialParameters::new(options.pub_key_cred_params)?;
103        let clientdata =
104            WinClientData::new(creation_to_clientdata(origin, options.challenge.clone()))?;
105
106        let mut exclude_credentials = if let Some(e) = options.exclude_credentials {
107            Some(WinCredentialList::new(e)?)
108        } else {
109            None
110        };
111        let extensions = match options.extensions {
112            Some(e) => WinExtensionsRequest::new(e)?,
113            None => Box::pin(WinExtensionsRequest::<WinExtensionMakeCredentialRequest>::default()),
114        };
115        // trace!("native extn: {:?}", extensions.native_ptr());
116
117        let makecredopts = WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS {
118            dwVersion: WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_CURRENT_VERSION,
119            dwTimeoutMilliseconds: timeout_ms,
120            // Superceded by pExcludeCredentialList for v3 (API v1, baseline)
121            CredentialList: WEBAUTHN_CREDENTIALS {
122                cCredentials: 0,
123                pCredentials: [].as_mut_ptr(),
124            },
125            Extensions: *extensions.native_ptr(),
126            dwAuthenticatorAttachment: attachment_to_native(
127                options
128                    .authenticator_selection
129                    .as_ref()
130                    .map(|s| s.authenticator_attachment)
131                    .unwrap_or(None),
132            ),
133            bRequireResidentKey: options
134                .authenticator_selection
135                .as_ref()
136                .map(|s| s.require_resident_key)
137                .unwrap_or(false)
138                .into(),
139            dwUserVerificationRequirement: user_verification_to_native(
140                options
141                    .authenticator_selection
142                    .as_ref()
143                    .map(|s| &s.user_verification),
144            ),
145            dwAttestationConveyancePreference: 0,
146            dwFlags: 0,
147            pCancellationId: std::ptr::null_mut(),
148            pExcludeCredentialList: match &mut exclude_credentials {
149                None => std::ptr::null(),
150                Some(l) => &l.native,
151            } as *mut _,
152            dwEnterpriseAttestation: 0,
153            dwLargeBlobSupport: 0,
154            bPreferResidentKey: false.into(),
155        };
156
157        // trace!("WebAuthNAuthenticatorMakeCredential()");
158        // trace!("native: {:?}", extensions.native_ptr());
159        // trace!(?makecredopts);
160        let a = unsafe {
161            let r = WebAuthNAuthenticatorMakeCredential(
162                &hwnd,
163                rp.native_ptr(),
164                userinfo.native_ptr(),
165                pubkeycredparams.native_ptr(),
166                clientdata.native_ptr(),
167                Some(&makecredopts),
168            )
169            .map_err(|e| {
170                // TODO: map error codes, if we learn them...
171                error!("Error: {:?}", e);
172                WebauthnCError::Internal
173            })?;
174
175            WinPtr::new(r, |a| WebAuthNFreeCredentialAttestation(Some(a)))
176                .ok_or(WebauthnCError::Internal)?
177        };
178        // These needed to live until WebAuthNAuthenticatorMakeCredential returned.
179        drop(extensions);
180        drop(hwnd);
181
182        // trace!("got result from WebAuthNAuthenticatorMakeCredential");
183        // trace!("{:?}", (*a));
184
185        unsafe {
186            let cred_id = from_raw_parts(a.pbCredentialId, a.cbCredentialId as usize).to_vec();
187            let attesation_object =
188                from_raw_parts(a.pbAttestationObject, a.cbAttestationObject as usize).to_vec();
189            let type_: String = a
190                .pwszFormatType
191                .to_string()
192                .map_err(|_| WebauthnCError::Internal)?;
193
194            Ok(RegisterPublicKeyCredential {
195                id: BASE64_ENGINE.encode(&cred_id),
196                raw_id: cred_id.into(),
197                type_,
198                extensions: native_to_registration_extensions(&a.Extensions)?,
199                response: AuthenticatorAttestationResponseRaw {
200                    attestation_object: attesation_object.into(),
201                    client_data_json: Base64UrlSafeData::from(
202                        clientdata.client_data_json().as_bytes().to_vec(),
203                    ),
204                    transports: Some(native_to_transports(a.dwUsedTransport)),
205                },
206            })
207        }
208    }
209
210    /// Perform an authentication action using Windows WebAuth API.
211    ///
212    /// This wraps [WebAuthNAuthenticatorGetAssertion].
213    ///
214    /// [WebAuthNAuthenticatorGetAssertion]: https://learn.microsoft.com/en-us/windows/win32/api/webauthn/nf-webauthn-webauthnauthenticatorgetassertion
215    fn perform_auth(
216        &mut self,
217        origin: Url,
218        options: PublicKeyCredentialRequestOptions,
219        timeout_ms: u32,
220    ) -> Result<PublicKeyCredential, WebauthnCError> {
221        trace!(?options);
222        let hwnd = Window::new()?;
223        let rp_id: HSTRING = options.rp_id.clone().into();
224        let clientdata = WinClientData::new(get_to_clientdata(origin, options.challenge.clone()))?;
225
226        let mut allow_credentials = WinCredentialList::new(options.allow_credentials)?;
227
228        let app_id: Option<HSTRING> = options
229            .extensions
230            .as_ref()
231            .and_then(|e| e.appid.as_ref())
232            .map(|a| a.clone().into());
233        // Used as a *return* value from GetAssertion as to whether the U2F AppId was used,
234        // equivalent to [AuthenticationExtensionsClientOutputs::appid].
235        //
236        // Why here? Because for some reason, Windows' API decides to put a pointer for
237        // mutable *return* value inside an `_In_opt_ *const ptr` *request* value
238        // ([WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS]): `pbU2fAppId`.
239        //
240        // The documentation was very opaque here, but [Firefox's implementation][ffx]
241        // appears to correctly deal with this nonsense.
242        //
243        // However, [Chromium's implementation][chr] appears to have misunderstood this field,
244        // and always passes in pointers to `static BOOL` values `kUseAppIdTrue` or
245        // `kUseAppIdFalse` (depending on whether the extension was present) and doesn't read
246        // the response.
247        //
248        // Unfortunately, it looks like the WebAuthn API has been frozen for Windows 10, and
249        // the new revisions are only on Windows 11. So it's unlikely this will ever be
250        // properly fixed. 🙃
251        //
252        // [chr]: https://chromium.googlesource.com/chromium/src/+/f62b8f341c14be84c6c995133f485d76a58de090/device/fido/win/webauthn_api.cc#520
253        // [ffx]: https://github.com/mozilla/gecko-dev/blob/620490a051a1fc72563e1c6bbecfe7346122a6bc/dom/webauthn/WinWebAuthnManager.cpp#L714-L716
254        let mut app_id_used: BOOL = false.into();
255        // let extensions = match &options.extensions {
256        //     Some(e) => WinExtensionsRequest::new(e)?,
257        //     None => Box::pin(WinExtensionsRequest::<WinExtensionGetAssertionRequest>::default()),
258        // };
259
260        let getassertopts = WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS {
261            dwVersion: WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_CURRENT_VERSION,
262            dwTimeoutMilliseconds: timeout_ms,
263            // Supersceded by pAllowCredentialList in v4 (API v1, baseline)
264            CredentialList: WEBAUTHN_CREDENTIALS {
265                cCredentials: 0,
266                pCredentials: [].as_mut_ptr(),
267            },
268            // Extensions: *extensions.native_ptr(),
269            Extensions: Default::default(),
270            dwAuthenticatorAttachment: 0, // Not supported?
271            dwUserVerificationRequirement: user_verification_to_native(Some(
272                &options.user_verification,
273            )),
274            dwFlags: 0,
275            pwszU2fAppId: match &app_id {
276                None => PCWSTR::null(),
277                Some(l) => l.into(),
278            },
279            pbU2fAppId: std::ptr::addr_of_mut!(app_id_used),
280            pCancellationId: std::ptr::null_mut(),
281            pAllowCredentialList: &mut allow_credentials.native,
282            dwCredLargeBlobOperation: 0,
283            cbCredLargeBlob: 0,
284            pbCredLargeBlob: std::ptr::null_mut(),
285        };
286
287        // trace!("WebAuthNAuthenticatorGetAssertion()");
288        let a = unsafe {
289            let r = WebAuthNAuthenticatorGetAssertion(
290                &hwnd,
291                &rp_id,
292                clientdata.native_ptr(),
293                Some(&getassertopts),
294            )
295            .map_err(|e| {
296                // TODO: map error codes, if we learn them...
297                error!("Error: {:?}", e);
298                WebauthnCError::Internal
299            })?;
300
301            WinPtr::new(r, WebAuthNFreeAssertion).ok_or(WebauthnCError::Internal)?
302        };
303        // This needed to live until WebAuthNAuthenticatorGetAssertion returned.
304        drop(hwnd);
305        // trace!("got result from WebAuthNAuthenticatorGetAssertion");
306
307        unsafe {
308            let user_id = from_raw_parts(a.pbUserId, a.cbUserId as usize).to_vec();
309            let authenticator_data =
310                from_raw_parts(a.pbAuthenticatorData, a.cbAuthenticatorData as usize).to_vec();
311            let signature = from_raw_parts(a.pbSignature, a.cbSignature as usize).to_vec();
312
313            let credential_id =
314                from_raw_parts(a.Credential.pbId, a.Credential.cbId as usize).to_vec();
315            let type_ = a
316                .Credential
317                .pwszCredentialType
318                .to_string()
319                .map_err(|_| WebauthnCError::Internal)?;
320
321            let mut extensions = if a.dwVersion >= 2 {
322                native_to_assertion_extensions(&a.Extensions)?
323            } else {
324                Default::default()
325            };
326            extensions.appid = Some(app_id_used.into());
327
328            Ok(PublicKeyCredential {
329                id: BASE64_ENGINE.encode(&credential_id),
330                raw_id: credential_id.into(),
331                response: AuthenticatorAssertionResponseRaw {
332                    authenticator_data: authenticator_data.into(),
333                    client_data_json: Base64UrlSafeData::from(
334                        clientdata.client_data_json().as_bytes().to_vec(),
335                    ),
336                    signature: signature.into(),
337                    user_handle: Some(user_id.into()),
338                },
339                type_,
340                extensions,
341            })
342        }
343    }
344}
345
346#[cfg(feature = "win10")]
347/// Converts an [AuthenticatorAttachment] into a value for
348/// [WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS::dwAuthenticatorAttachment]
349fn attachment_to_native(attachment: Option<AuthenticatorAttachment>) -> u32 {
350    use AuthenticatorAttachment::*;
351    match attachment {
352        None => WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY,
353        Some(CrossPlatform) => WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM,
354        Some(Platform) => WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM,
355    }
356}
357
358#[cfg(feature = "win10")]
359/// Converts a [UserVerificationPolicy] into a value for
360/// [WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS::dwUserVerificationRequirement]
361fn user_verification_to_native(policy: Option<&UserVerificationPolicy>) -> u32 {
362    use UserVerificationPolicy::*;
363    match policy {
364        None => WEBAUTHN_USER_VERIFICATION_REQUIREMENT_ANY,
365        Some(p) => match p {
366            Required => WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED,
367            Preferred => WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED,
368            Discouraged_DO_NOT_USE => WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED,
369        },
370    }
371}