webauthn_authenticator_rs/usb/
mod.rs

1//! [USBTransport] communicates with a FIDO token over USB HID.
2//!
3//! This module should work on most platforms with USB support, provided that
4//! the user has permissions.
5//!
6//! ## Windows support
7//!
8//! Windows' WebAuthn API (on Windows 10 build 1903 and later) blocks
9//! non-Administrator access to **all** USB HID FIDO tokens, making them
10//! invisible to normal USB HID APIs.
11//!
12//! Use [Win10][crate::win10::Win10] (available with the `win10` feature) on
13//! Windows instead.
14mod framing;
15mod responses;
16#[cfg(any(all(doc, not(doctest)), feature = "vendor-solokey"))]
17mod solokey;
18#[cfg(any(all(doc, not(doctest)), feature = "vendor-yubikey"))]
19mod yubikey;
20
21use fido_hid_rs::{
22    HidReportBytes, HidSendReportBytes, USBDevice, USBDeviceImpl, USBDeviceInfo, USBDeviceInfoImpl,
23    USBDeviceManager, USBDeviceManagerImpl, WatchEvent,
24};
25
26use crate::error::WebauthnCError;
27use crate::transport::types::{
28    KeepAliveStatus, Response, U2FHID_CANCEL, U2FHID_CBOR, U2FHID_INIT, U2FHID_WINK,
29};
30use crate::transport::*;
31use crate::ui::UiCallback;
32use crate::usb::framing::*;
33use async_trait::async_trait;
34use futures::stream::BoxStream;
35use futures::StreamExt as _;
36
37#[cfg(doc)]
38use crate::stubs::*;
39
40use openssl::rand::rand_bytes;
41use std::fmt;
42use std::time::Duration;
43use webauthn_rs_proto::AuthenticatorTransport;
44
45pub(crate) use self::responses::InitResponse;
46
47// u2f_hid.h
48const CID_BROADCAST: u32 = 0xffffffff;
49
50pub struct USBTransport {
51    manager: USBDeviceManagerImpl,
52}
53
54pub struct USBToken {
55    device: USBDeviceImpl,
56    cid: u32,
57    supports_ctap1: bool,
58    supports_ctap2: bool,
59    supports_wink: bool,
60    initialised: bool,
61}
62
63impl fmt::Debug for USBTransport {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        f.debug_struct("USBTransport").finish()
66    }
67}
68
69impl fmt::Debug for USBToken {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        f.debug_struct("USBToken")
72            .field("cid", &self.cid)
73            .field("supports_ctap1", &self.supports_ctap1)
74            .field("supports_ctap2", &self.supports_ctap2)
75            .field("supports_wink", &self.supports_wink)
76            .field("initialised", &self.initialised)
77            .finish()
78    }
79}
80
81impl USBTransport {
82    pub async fn new() -> Result<Self, WebauthnCError> {
83        Ok(Self {
84            manager: USBDeviceManager::new().await?,
85        })
86    }
87}
88
89#[async_trait]
90impl<'b> Transport<'b> for USBTransport {
91    type Token = USBToken;
92
93    async fn watch(&self) -> Result<BoxStream<TokenEvent<Self::Token>>, WebauthnCError> {
94        let ret = self.manager.watch_devices().await?;
95
96        Ok(Box::pin(ret.filter_map(|event| async move {
97            trace!("USB event: {event:?}");
98            match event {
99                WatchEvent::Added(d) => {
100                    if let Ok(dev) = d.open().await {
101                        let mut token = USBToken::new(dev);
102                        if let Ok(()) = token.init().await {
103                            Some(TokenEvent::Added(token))
104                        } else {
105                            None
106                        }
107                    } else {
108                        None
109                    }
110                }
111                WatchEvent::Removed(i) => Some(TokenEvent::Removed(i)),
112                WatchEvent::EnumerationComplete => Some(TokenEvent::EnumerationComplete),
113            }
114        })))
115    }
116
117    /// Gets a list of attached USB HID FIDO tokens.
118    ///
119    /// Any un-openable devices will be silently ignored.
120    ///
121    /// ## Platform-specific issues
122    ///
123    /// ### Linux
124    ///
125    /// systemd (udev) v252 and later [automatically tag USB HID FIDO tokens][1]
126    /// and set permissions based on the `f1d0` usage page, which should work
127    /// with any FIDO-compliant token.
128    ///
129    /// Previously, most distributions used a fixed list of device IDs, which
130    /// can be a problem for new or esoteric tokens.
131    ///
132    /// [1]: https://github.com/systemd/systemd/issues/11996
133    ///
134    /// ### Windows
135    ///
136    /// On Windows 10 build 1903 or later, this will not return any devices
137    /// unless the program is run as Administrator.
138    async fn tokens(&self) -> Result<Vec<Self::Token>, WebauthnCError> {
139        Ok(futures::stream::iter(self.manager.get_devices().await?)
140            .filter_map(|d| async move {
141                if let Ok(dev) = d.open().await {
142                    Some(USBToken::new(dev))
143                } else {
144                    None
145                }
146            })
147            .collect()
148            .await)
149    }
150}
151
152impl USBToken {
153    fn new(device: USBDeviceImpl) -> Self {
154        USBToken {
155            device, // : Mutex::new(device),
156            cid: 0,
157            supports_ctap1: false,
158            supports_ctap2: false,
159            supports_wink: false,
160            initialised: false,
161        }
162    }
163
164    /// Sends a single [U2FHIDFrame] to the device, without fragmentation.
165    async fn send_one(&mut self, frame: &U2FHIDFrame) -> Result<(), WebauthnCError> {
166        let d: HidSendReportBytes = frame.into();
167        trace!(">>> {}", hex::encode(d));
168        // let guard = self.device.lock()?;
169        self.device.write(d).await?;
170        Ok(())
171    }
172
173    /// Sends a [U2FHIDFrame] to the device, fragmenting the message to fit
174    /// within the USB HID MTU.
175    async fn send(&mut self, frame: &U2FHIDFrame) -> Result<(), WebauthnCError> {
176        for f in U2FHIDFrameIterator::new(frame)? {
177            self.send_one(&f).await?;
178        }
179        Ok(())
180    }
181
182    /// Receives a single [U2FHIDFrame] from the device, without fragmentation.
183    async fn recv_one(&mut self) -> Result<U2FHIDFrame, WebauthnCError> {
184        let ret: HidReportBytes = async {
185            // let guard = self.device.lock()?;
186            let ret = self.device.read().await?;
187            Ok::<HidReportBytes, WebauthnCError>(ret)
188        }
189        .await?;
190
191        trace!("<<< {}", hex::encode(ret));
192        U2FHIDFrame::try_from(&ret)
193    }
194
195    /// Recives a [Response] from the device, handling fragmented [U2FHIDFrame]
196    /// responses if needed.
197    async fn recv(&mut self) -> Result<Response, WebauthnCError> {
198        // Recieve first chunk
199        let mut f = self.recv_one().await?;
200        let mut s: usize = f.data.len();
201        let t = usize::from(f.len);
202
203        // Get more chunks, if needed
204        while s < t {
205            let n = self.recv_one().await?;
206            s += n.data.len();
207            f += n;
208        }
209        Response::try_from(&f)
210    }
211
212    /// Return whether the token supports the wink function.
213    pub fn supports_wink(&self) -> bool {
214        self.supports_wink
215    }
216
217    /// Wink the device.
218    ///
219    /// This performs a vendor-defined action that provides some visual or audible
220    /// identification of the device.
221    pub async fn wink(&mut self) -> Result<(), WebauthnCError> {
222        if !self.initialised {
223            error!("Attempted to transmit to uninitialised token");
224            return Err(WebauthnCError::Internal);
225        }
226        if !self.supports_wink {
227            error!("Token does not support the wink function");
228            return Err(WebauthnCError::NotSupported);
229        }
230
231        self.send(&U2FHIDFrame {
232            cid: self.cid,
233            cmd: U2FHID_WINK,
234            len: 0,
235            data: vec![],
236        })
237        .await?;
238
239        match self.recv().await? {
240            Response::Wink => Ok(()),
241            e => {
242                error!("Unhandled response type: {:?}", e);
243                Err(WebauthnCError::Internal)
244            }
245        }
246    }
247}
248
249#[async_trait]
250impl Token for USBToken {
251    // TODO: platform code
252    type Id = <USBDeviceInfoImpl as USBDeviceInfo>::Id;
253
254    async fn transmit_raw<U>(&mut self, cmd: &[u8], ui: &U) -> Result<Vec<u8>, WebauthnCError>
255    where
256        U: UiCallback,
257    {
258        if !self.initialised {
259            error!("attempted to transmit to uninitialised token");
260            return Err(WebauthnCError::Internal);
261        }
262
263        let cmd = U2FHIDFrame {
264            cid: self.cid,
265            cmd: U2FHID_CBOR,
266            len: cmd.len() as u16,
267            data: cmd.to_vec(),
268        };
269        self.send(&cmd).await?;
270
271        // Get a response, checking for keep-alive
272        let resp = loop {
273            let resp = self.recv().await?;
274
275            if let Response::KeepAlive(r) = resp {
276                trace!("waiting for {:?}", r);
277                match r {
278                    KeepAliveStatus::UserPresenceNeeded => ui.request_touch(),
279                    KeepAliveStatus::Processing => ui.processing(),
280                    _ => (),
281                }
282                // TODO: maybe time out at some point
283                tokio::time::sleep(Duration::from_millis(100)).await;
284            } else {
285                break resp;
286            }
287        };
288
289        // Get a response
290        match resp {
291            Response::Cbor(c) => {
292                if c.status.is_ok() {
293                    Ok(c.data)
294                } else {
295                    let e = WebauthnCError::Ctap(c.status);
296                    error!("Ctap error: {:?}", e);
297                    Err(e)
298                }
299            }
300            e => {
301                error!("Unhandled response type: {:?}", e);
302                Err(WebauthnCError::Cbor)
303            }
304        }
305    }
306
307    async fn init(&mut self) -> Result<(), WebauthnCError> {
308        if self.initialised {
309            warn!("attempted to init an already-initialised token");
310            return Ok(());
311        }
312
313        // Setup a channel to communicate with the device (CTAPHID_INIT).
314        let mut nonce: [u8; 8] = [0; 8];
315        rand_bytes(&mut nonce)?;
316
317        self.send(&U2FHIDFrame {
318            cid: CID_BROADCAST,
319            cmd: U2FHID_INIT,
320            len: nonce.len() as u16,
321            data: nonce.to_vec(),
322        })
323        .await?;
324
325        match self.recv().await? {
326            Response::Init(i) => {
327                trace!(?i);
328                assert_eq!(&nonce, &i.nonce[..]);
329                self.cid = i.cid;
330                self.supports_ctap1 = i.supports_ctap1();
331                self.supports_ctap2 = i.supports_ctap2();
332                self.supports_wink = i.supports_wink();
333
334                if self.supports_ctap2 {
335                    self.initialised = true;
336                    Ok(())
337                } else {
338                    error!("token does not support CTAP 2");
339                    Err(WebauthnCError::NotSupported)
340                }
341            }
342            e => {
343                error!("Unhandled response type: {:?}", e);
344                Err(WebauthnCError::Internal)
345            }
346        }
347    }
348
349    async fn close(&mut self) -> Result<(), WebauthnCError> {
350        Ok(())
351    }
352
353    fn get_transport(&self) -> AuthenticatorTransport {
354        AuthenticatorTransport::Usb
355    }
356
357    async fn cancel(&mut self) -> Result<(), WebauthnCError> {
358        if !self.initialised {
359            error!("attempted to cancel uninitialised token");
360            return Err(WebauthnCError::Internal);
361        }
362
363        let cmd = U2FHIDFrame {
364            cid: self.cid,
365            cmd: U2FHID_CANCEL,
366            len: 0,
367            data: vec![],
368        };
369        self.send_one(&cmd).await
370    }
371}