webauthn_authenticator_rs/usb/
mod.rs1mod 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
47const 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 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, cid: 0,
157 supports_ctap1: false,
158 supports_ctap2: false,
159 supports_wink: false,
160 initialised: false,
161 }
162 }
163
164 async fn send_one(&mut self, frame: &U2FHIDFrame) -> Result<(), WebauthnCError> {
166 let d: HidSendReportBytes = frame.into();
167 trace!(">>> {}", hex::encode(d));
168 self.device.write(d).await?;
170 Ok(())
171 }
172
173 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 async fn recv_one(&mut self) -> Result<U2FHIDFrame, WebauthnCError> {
184 let ret: HidReportBytes = async {
185 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 async fn recv(&mut self) -> Result<Response, WebauthnCError> {
198 let mut f = self.recv_one().await?;
200 let mut s: usize = f.data.len();
201 let t = usize::from(f.len);
202
203 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 pub fn supports_wink(&self) -> bool {
214 self.supports_wink
215 }
216
217 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 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 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 tokio::time::sleep(Duration::from_millis(100)).await;
284 } else {
285 break resp;
286 }
287 };
288
289 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 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}