From da72ad59f1659488fe8e19f6b308cfe5baafc19f Mon Sep 17 00:00:00 2001 From: Michael Farrell Date: Wed, 25 Oct 2023 17:36:12 +1000 Subject: [PATCH] add solo-key-version, docs --- Cargo.toml | 1 + fido-key-manager/src/main.rs | 37 +++++++++++++- .../src/bluetooth/mod.rs | 4 +- .../src/ctap2/solokey.rs | 24 ++++++++- .../src/transport/solokey.rs | 50 +++++++++++++++++++ .../src/transport/types.rs | 1 + .../src/usb/responses.rs | 11 ++-- webauthn-authenticator-rs/src/usb/solokey.rs | 29 ++++++++++- 8 files changed, 147 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d1939adf..a0409da0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,7 @@ tokio = { version = "1.22.0", features = [ "sync", "test-util", "macros", + "net", "rt-multi-thread", "time", ] } diff --git a/fido-key-manager/src/main.rs b/fido-key-manager/src/main.rs index cd3aa745..1b198e72 100644 --- a/fido-key-manager/src/main.rs +++ b/fido-key-manager/src/main.rs @@ -203,6 +203,9 @@ pub enum Opt { #[cfg(feature = "solokey")] /// Gets a SoloKey's UUID. SoloKeyUuid(InfoOpt), + #[cfg(feature = "solokey")] + /// Gets a SoloKey's version. + SoloKeyVersion(InfoOpt), } #[derive(Debug, clap::Parser)] @@ -697,7 +700,7 @@ async fn main() { None => continue, }; - match authenticator.get_uuid().await { + match authenticator.get_solokey_uuid().await { Ok(uuid) => println!("SoloKey UUID: {uuid}"), Err(WebauthnCError::NotSupported) | Err(WebauthnCError::InvalidMessageLength) => { @@ -718,5 +721,37 @@ async fn main() { } } } + + #[cfg(feature = "solokey")] + Opt::SoloKeyVersion(o) => { + while let Some(event) = stream.next().await { + match event { + TokenEvent::Added(t) => { + let mut authenticator = match CtapAuthenticator::new(t, &ui).await { + Some(a) => a, + None => continue, + }; + + match authenticator.get_solokey_version().await { + Ok(ver) => println!("SoloKey version: {ver:#x}"), + Err(WebauthnCError::NotSupported) + | Err(WebauthnCError::InvalidMessageLength) => { + println!("Device is not a SoloKey!") + } + Err(e) => panic!("could not get SoloKey version: {e:?}"), + } + } + TokenEvent::EnumerationComplete => { + if o.watch { + println!("Initial enumeration completed, watching for more devices..."); + println!("Press Ctrl + C to stop watching."); + } else { + break; + } + } + _ => (), + } + } + } } } diff --git a/webauthn-authenticator-rs/src/bluetooth/mod.rs b/webauthn-authenticator-rs/src/bluetooth/mod.rs index b75ed707..985aeefd 100644 --- a/webauthn-authenticator-rs/src/bluetooth/mod.rs +++ b/webauthn-authenticator-rs/src/bluetooth/mod.rs @@ -75,9 +75,9 @@ use crate::{ transport::{ types::{ CBORResponse, KeepAliveStatus, Response, U2FError, BTLE_CANCEL, BTLE_KEEPALIVE, - TYPE_INIT, U2FHID_ERROR, U2FHID_MSG, U2FHID_PING, + U2FHID_ERROR, U2FHID_MSG, U2FHID_PING, }, - Token, TokenEvent, Transport, + Token, TokenEvent, Transport, TYPE_INIT, }, ui::UiCallback, }; diff --git a/webauthn-authenticator-rs/src/ctap2/solokey.rs b/webauthn-authenticator-rs/src/ctap2/solokey.rs index c42cfc10..fd73c6ed 100644 --- a/webauthn-authenticator-rs/src/ctap2/solokey.rs +++ b/webauthn-authenticator-rs/src/ctap2/solokey.rs @@ -7,16 +7,36 @@ use crate::{ use super::Ctap20Authenticator; +/// SoloKey (Trussed) vendor-specific commands. +/// +/// ## Warning +/// +/// These commands currently operate on *any* [`Ctap20Authenticator`][], and do +/// not filter to just SoloKey/Trussed devices. Due to the nature of CTAP +/// vendor-specific commands, this may cause unexpected or undesirable behaviour +/// on other vendors' keys. +/// +/// Protocol notes are in [`crate::transport::solokey`]. #[async_trait] pub trait SoloKeyAuthenticator { - async fn get_uuid(&mut self) -> Result; + /// Gets the device-specific UUID of a SoloKey token. + async fn get_solokey_uuid(&mut self) -> Result; + + /// Gets the version of a SoloKey token. + async fn get_solokey_version(&mut self) -> Result; } #[async_trait] impl<'a, T: Token + SoloKeyToken, U: UiCallback> SoloKeyAuthenticator for Ctap20Authenticator<'a, T, U> { - async fn get_uuid(&mut self) -> Result { + #[inline] + async fn get_solokey_uuid(&mut self) -> Result { self.token.get_solokey_uuid().await } + + #[inline] + async fn get_solokey_version(&mut self) -> Result { + self.token.get_solokey_version().await + } } diff --git a/webauthn-authenticator-rs/src/transport/solokey.rs b/webauthn-authenticator-rs/src/transport/solokey.rs index 09746015..fa63e0e3 100644 --- a/webauthn-authenticator-rs/src/transport/solokey.rs +++ b/webauthn-authenticator-rs/src/transport/solokey.rs @@ -1,3 +1,30 @@ +//! SoloKey (Trussed) vendor-specific commands. +//! +//! ## USB HID +//! +//! Commands are sent on a `U2FHIDFrame` level, and values are bitwise-OR'd +//! with `transport::TYPE_INIT` (0x80). +//! +//! Command | Description | Request | Response +//! ------- | ----------- | ------- | -------- +//! `0x51` | Update | _none_ to reboot into update mode, `01` to be "destructive" | _none_ +//! `0x53` | Reboot | _none_ | _none_ +//! `0x60` | Get random bytes | _none_ | 57 bytes of randomness +//! `0x61` | Get version | _none_ | Version ID as `u32` +//! `0x62` | Get device UUID | _none_ | Big-endian UUID (16 bytes) +//! `0x63` | Get lock state | _none_ | `0` for unlocked "hacker edition" devices, `1` if locked +//! +//! ## NFC +//! +//! Admin app AID: `A0 00 00 08 47 00 00 00 01` +//! +//! ## References +//! +//! * [`solo2-cli` Admin commands][0] +//! * [SoloKeys `admin-app` commands][1] +//! +//! [0]: https://github.com/solokeys/solo2-cli/blob/main/src/apps/admin.rs +//! [1]: https://github.com/solokeys/admin-app/blob/main/src/admin.rs use async_trait::async_trait; use uuid::Uuid; @@ -5,16 +32,39 @@ use crate::prelude::WebauthnCError; use super::AnyToken; +#[cfg(all(feature = "usb", feature = "vendor-solokey"))] +pub const CMD_RAND: u8 = super::TYPE_INIT | 0x60; + +#[cfg(all(feature = "usb", feature = "vendor-solokey"))] +pub const CMD_VERSION: u8 = super::TYPE_INIT | 0x61; + #[cfg(all(feature = "usb", feature = "vendor-solokey"))] pub const CMD_UUID: u8 = super::TYPE_INIT | 0x62; +/// See [`SoloKeyAuthenticator`](crate::ctap2::SoloKeyAuthenticator). #[async_trait] pub trait SoloKeyToken { + /// See [`SoloKeyAuthenticator::get_solokey_version()`](crate::ctap2::SoloKeyAuthenticator::get_solokey_version). + async fn get_solokey_version(&mut self) -> Result; + + /// See [`SoloKeyAuthenticator::get_solokey_uuid()`](crate::ctap2::SoloKeyAuthenticator::get_solokey_uuid). async fn get_solokey_uuid(&mut self) -> Result; } #[async_trait] impl SoloKeyToken for AnyToken { + async fn get_solokey_version(&mut self) -> Result { + match self { + AnyToken::Stub => unimplemented!(), + #[cfg(feature = "bluetooth")] + AnyToken::Bluetooth(_) => Err(WebauthnCError::NotSupported), + #[cfg(feature = "nfc")] + AnyToken::Nfc(_) => Err(WebauthnCError::NotSupported), + #[cfg(feature = "usb")] + AnyToken::Usb(u) => u.get_solokey_version().await, + } + } + async fn get_solokey_uuid(&mut self) -> Result { match self { AnyToken::Stub => unimplemented!(), diff --git a/webauthn-authenticator-rs/src/transport/types.rs b/webauthn-authenticator-rs/src/transport/types.rs index ad287fc5..c48a05d2 100644 --- a/webauthn-authenticator-rs/src/transport/types.rs +++ b/webauthn-authenticator-rs/src/transport/types.rs @@ -1,3 +1,4 @@ +use super::TYPE_INIT; use crate::error::{CtapError, WebauthnCError}; #[cfg(any(all(doc, not(doctest)), feature = "usb"))] diff --git a/webauthn-authenticator-rs/src/usb/responses.rs b/webauthn-authenticator-rs/src/usb/responses.rs index a8e20505..28b55420 100644 --- a/webauthn-authenticator-rs/src/usb/responses.rs +++ b/webauthn-authenticator-rs/src/usb/responses.rs @@ -1,9 +1,12 @@ //! All [Response] frame types, used by FIDO tokens over USB HID. use crate::error::WebauthnCError; -use crate::transport::iso7816::ISO7816ResponseAPDU; -use crate::transport::types::{ - CBORResponse, U2FError, TYPE_INIT, U2FHID_CBOR, U2FHID_ERROR, U2FHID_KEEPALIVE, U2FHID_MSG, - U2FHID_PING, +use crate::transport::{ + iso7816::ISO7816ResponseAPDU, + types::{ + CBORResponse, U2FError, U2FHID_CBOR, U2FHID_ERROR, U2FHID_KEEPALIVE, U2FHID_MSG, + U2FHID_PING, + }, + TYPE_INIT, }; use crate::usb::framing::U2FHIDFrame; use crate::usb::*; diff --git a/webauthn-authenticator-rs/src/usb/solokey.rs b/webauthn-authenticator-rs/src/usb/solokey.rs index e82c12e7..1dc0f22f 100644 --- a/webauthn-authenticator-rs/src/usb/solokey.rs +++ b/webauthn-authenticator-rs/src/usb/solokey.rs @@ -4,7 +4,7 @@ use uuid::Uuid; use crate::{ prelude::WebauthnCError, transport::{ - solokey::{SoloKeyToken, CMD_UUID}, + solokey::{SoloKeyToken, CMD_UUID, CMD_VERSION}, types::{U2FError, U2FHID_ERROR}, }, usb::framing::U2FHIDFrame, @@ -14,6 +14,33 @@ use super::{USBToken, USBTransport}; #[async_trait] impl SoloKeyToken for USBToken { + async fn get_solokey_version(&mut self) -> Result { + let cmd = U2FHIDFrame { + cid: self.cid, + cmd: CMD_VERSION, + len: 0, + data: vec![], + }; + self.send_one(&cmd).await?; + + let r = self.recv_one().await?; + match r.cmd { + CMD_VERSION => { + let u = u32::from_be_bytes( + r.data + .try_into() + .map_err(|_| WebauthnCError::InvalidMessageLength)?, + ); + + Ok(u) + } + + U2FHID_ERROR => Err(U2FError::from(r.data.as_slice()).into()), + + _ => Err(WebauthnCError::UnexpectedState), + } + } + async fn get_solokey_uuid(&mut self) -> Result { let cmd = U2FHIDFrame { cid: self.cid,