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::{KeepAliveStatus, Response, U2FHID_CANCEL, U2FHID_CBOR, U2FHID_INIT};
28use crate::transport::*;
29use crate::ui::UiCallback;
30use crate::usb::framing::*;
31use async_trait::async_trait;
32use futures::stream::BoxStream;
33use futures::StreamExt as _;
34
35#[cfg(doc)]
36use crate::stubs::*;
37
38use openssl::rand::rand_bytes;
39use std::fmt;
40use std::time::Duration;
41use webauthn_rs_proto::AuthenticatorTransport;
42
43pub(crate) use self::responses::InitResponse;
44
45const CID_BROADCAST: u32 = 0xffffffff;
47
48pub struct USBTransport {
49 manager: USBDeviceManagerImpl,
50}
51
52pub struct USBToken {
53 device: USBDeviceImpl,
54 cid: u32,
55 supports_ctap1: bool,
56 supports_ctap2: bool,
57 initialised: bool,
58}
59
60impl fmt::Debug for USBTransport {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 f.debug_struct("USBTransport").finish()
63 }
64}
65
66impl fmt::Debug for USBToken {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 f.debug_struct("USBToken")
69 .field("cid", &self.cid)
70 .field("supports_ctap1", &self.supports_ctap1)
71 .field("supports_ctap2", &self.supports_ctap2)
72 .field("initialised", &self.initialised)
73 .finish()
74 }
75}
76
77impl USBTransport {
78 pub async fn new() -> Result<Self, WebauthnCError> {
79 Ok(Self {
80 manager: USBDeviceManager::new().await?,
81 })
82 }
83}
84
85#[async_trait]
86impl<'b> Transport<'b> for USBTransport {
87 type Token = USBToken;
88
89 async fn watch(&self) -> Result<BoxStream<TokenEvent<Self::Token>>, WebauthnCError> {
90 let ret = self.manager.watch_devices().await?;
91
92 Ok(Box::pin(ret.filter_map(|event| async move {
93 trace!("USB event: {event:?}");
94 match event {
95 WatchEvent::Added(d) => {
96 if let Ok(dev) = d.open().await {
97 let mut token = USBToken::new(dev);
98 if let Ok(()) = token.init().await {
99 Some(TokenEvent::Added(token))
100 } else {
101 None
102 }
103 } else {
104 None
105 }
106 }
107 WatchEvent::Removed(i) => Some(TokenEvent::Removed(i)),
108 WatchEvent::EnumerationComplete => Some(TokenEvent::EnumerationComplete),
109 }
110 })))
111 }
112
113 async fn tokens(&self) -> Result<Vec<Self::Token>, WebauthnCError> {
135 Ok(futures::stream::iter(self.manager.get_devices().await?)
136 .filter_map(|d| async move {
137 if let Ok(dev) = d.open().await {
138 Some(USBToken::new(dev))
139 } else {
140 None
141 }
142 })
143 .collect()
144 .await)
145 }
146}
147
148impl USBToken {
149 fn new(device: USBDeviceImpl) -> Self {
150 USBToken {
151 device, cid: 0,
153 supports_ctap1: false,
154 supports_ctap2: false,
155 initialised: false,
156 }
157 }
158
159 async fn send_one(&mut self, frame: &U2FHIDFrame) -> Result<(), WebauthnCError> {
161 let d: HidSendReportBytes = frame.into();
162 trace!(">>> {}", hex::encode(d));
163 self.device.write(d).await?;
165 Ok(())
166 }
167
168 async fn send(&mut self, frame: &U2FHIDFrame) -> Result<(), WebauthnCError> {
171 for f in U2FHIDFrameIterator::new(frame)? {
172 self.send_one(&f).await?;
173 }
174 Ok(())
175 }
176
177 async fn recv_one(&mut self) -> Result<U2FHIDFrame, WebauthnCError> {
179 let ret: HidReportBytes = async {
180 let ret = self.device.read().await?;
182 Ok::<HidReportBytes, WebauthnCError>(ret)
183 }
184 .await?;
185
186 trace!("<<< {}", hex::encode(ret));
187 U2FHIDFrame::try_from(&ret)
188 }
189
190 async fn recv(&mut self) -> Result<Response, WebauthnCError> {
193 let mut f = self.recv_one().await?;
195 let mut s: usize = f.data.len();
196 let t = usize::from(f.len);
197
198 while s < t {
200 let n = self.recv_one().await?;
201 s += n.data.len();
202 f += n;
203 }
204 Response::try_from(&f)
205 }
206}
207
208#[async_trait]
209impl Token for USBToken {
210 type Id = <USBDeviceInfoImpl as USBDeviceInfo>::Id;
212
213 async fn transmit_raw<U>(&mut self, cmd: &[u8], ui: &U) -> Result<Vec<u8>, WebauthnCError>
214 where
215 U: UiCallback,
216 {
217 if !self.initialised {
218 error!("attempted to transmit to uninitialised token");
219 return Err(WebauthnCError::Internal);
220 }
221
222 let cmd = U2FHIDFrame {
223 cid: self.cid,
224 cmd: U2FHID_CBOR,
225 len: cmd.len() as u16,
226 data: cmd.to_vec(),
227 };
228 self.send(&cmd).await?;
229
230 let resp = loop {
232 let resp = self.recv().await?;
233
234 if let Response::KeepAlive(r) = resp {
235 trace!("waiting for {:?}", r);
236 match r {
237 KeepAliveStatus::UserPresenceNeeded => ui.request_touch(),
238 KeepAliveStatus::Processing => ui.processing(),
239 _ => (),
240 }
241 tokio::time::sleep(Duration::from_millis(100)).await;
243 } else {
244 break resp;
245 }
246 };
247
248 match resp {
250 Response::Cbor(c) => {
251 if c.status.is_ok() {
252 Ok(c.data)
253 } else {
254 let e = WebauthnCError::Ctap(c.status);
255 error!("Ctap error: {:?}", e);
256 Err(e)
257 }
258 }
259 e => {
260 error!("Unhandled response type: {:?}", e);
261 Err(WebauthnCError::Cbor)
262 }
263 }
264 }
265
266 async fn init(&mut self) -> Result<(), WebauthnCError> {
267 if self.initialised {
268 warn!("attempted to init an already-initialised token");
269 return Ok(());
270 }
271
272 let mut nonce: [u8; 8] = [0; 8];
274 rand_bytes(&mut nonce)?;
275
276 self.send(&U2FHIDFrame {
277 cid: CID_BROADCAST,
278 cmd: U2FHID_INIT,
279 len: nonce.len() as u16,
280 data: nonce.to_vec(),
281 })
282 .await?;
283
284 match self.recv().await? {
285 Response::Init(i) => {
286 trace!(?i);
287 assert_eq!(&nonce, &i.nonce[..]);
288 self.cid = i.cid;
289 self.supports_ctap1 = i.supports_ctap1();
290 self.supports_ctap2 = i.supports_ctap2();
291
292 if self.supports_ctap2 {
293 self.initialised = true;
294 Ok(())
295 } else {
296 error!("token does not support CTAP 2");
297 Err(WebauthnCError::NotSupported)
298 }
299 }
300 e => {
301 error!("Unhandled response type: {:?}", e);
302 Err(WebauthnCError::Internal)
303 }
304 }
305 }
306
307 async fn close(&mut self) -> Result<(), WebauthnCError> {
308 Ok(())
309 }
310
311 fn get_transport(&self) -> AuthenticatorTransport {
312 AuthenticatorTransport::Usb
313 }
314
315 async fn cancel(&mut self) -> Result<(), WebauthnCError> {
316 if !self.initialised {
317 error!("attempted to cancel uninitialised token");
318 return Err(WebauthnCError::Internal);
319 }
320
321 let cmd = U2FHIDFrame {
322 cid: self.cid,
323 cmd: U2FHID_CANCEL,
324 len: 0,
325 data: vec![],
326 };
327 self.send_one(&cmd).await
328 }
329}