diff --git a/fido-key-manager/src/main.rs b/fido-key-manager/src/main.rs index 1b198e72..394476e1 100644 --- a/fido-key-manager/src/main.rs +++ b/fido-key-manager/src/main.rs @@ -201,11 +201,8 @@ pub enum Opt { /// Updates user information for a discoverable credential on this token. UpdateCredentialUser(UpdateCredentialUserOpt), #[cfg(feature = "solokey")] - /// Gets a SoloKey's UUID. - SoloKeyUuid(InfoOpt), - #[cfg(feature = "solokey")] - /// Gets a SoloKey's version. - SoloKeyVersion(InfoOpt), + /// Gets info about a connected SoloKey. + SoloKeyInfo(InfoOpt), } #[derive(Debug, clap::Parser)] @@ -691,7 +688,7 @@ async fn main() { } #[cfg(feature = "solokey")] - Opt::SoloKeyUuid(o) => { + Opt::SoloKeyInfo(o) => { while let Some(event) = stream.next().await { match event { TokenEvent::Added(t) => { @@ -700,46 +697,47 @@ async fn main() { None => continue, }; - match authenticator.get_solokey_uuid().await { - Ok(uuid) => println!("SoloKey UUID: {uuid}"), + let uuid = match authenticator.get_solokey_uuid().await { + Ok(v) => v, Err(WebauthnCError::NotSupported) + | Err(WebauthnCError::U2F(_)) | Err(WebauthnCError::InvalidMessageLength) => { - println!("Device is not a SoloKey!") + println!("Device is not a SoloKey!"); + continue; } Err(e) => panic!("could not get SoloKey UUID: {e:?}"), - } - } - TokenEvent::EnumerationComplete => { - if o.watch { - println!("Initial enumeration completed, watching for more devices..."); - println!("Press Ctrl + C to stop watching."); - } else { - break; - } - } - _ => (), - } - } - } - - #[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}"), + let version = match authenticator.get_solokey_version().await { + Ok(v) => v, Err(WebauthnCError::NotSupported) + | Err(WebauthnCError::U2F(_)) | Err(WebauthnCError::InvalidMessageLength) => { - println!("Device is not a SoloKey!") + println!("Device is not a SoloKey!"); + continue; } Err(e) => panic!("could not get SoloKey version: {e:?}"), - } + }; + + let secure_boot = if match authenticator.get_solokey_lock().await { + Ok(v) => v, + Err(WebauthnCError::NotSupported) + | Err(WebauthnCError::U2F(_)) + | Err(WebauthnCError::InvalidMessageLength) => { + println!("Device is not a SoloKey!"); + continue; + } + Err(e) => panic!("could not get SoloKey lock state: {e:?}"), + } { + "enabled" + } else { + "disabled" + }; + + println!("SoloKey info:"); + println!(" Device UUID: {uuid}"); + println!(" Version: {version:#x}"); + println!(" Secure boot: {secure_boot}"); } TokenEvent::EnumerationComplete => { if o.watch { diff --git a/webauthn-authenticator-rs/src/ctap2/solokey.rs b/webauthn-authenticator-rs/src/ctap2/solokey.rs index fd73c6ed..6a44d9d7 100644 --- a/webauthn-authenticator-rs/src/ctap2/solokey.rs +++ b/webauthn-authenticator-rs/src/ctap2/solokey.rs @@ -19,6 +19,9 @@ use super::Ctap20Authenticator; /// Protocol notes are in [`crate::transport::solokey`]. #[async_trait] pub trait SoloKeyAuthenticator { + /// Gets the device's lock (secure boot) status. + async fn get_solokey_lock(&mut self) -> Result; + /// Gets the device-specific UUID of a SoloKey token. async fn get_solokey_uuid(&mut self) -> Result; @@ -30,6 +33,11 @@ pub trait SoloKeyAuthenticator { impl<'a, T: Token + SoloKeyToken, U: UiCallback> SoloKeyAuthenticator for Ctap20Authenticator<'a, T, U> { + #[inline] + async fn get_solokey_lock(&mut self) -> Result { + self.token.get_solokey_lock().await + } + #[inline] async fn get_solokey_uuid(&mut self) -> Result { self.token.get_solokey_uuid().await diff --git a/webauthn-authenticator-rs/src/transport/solokey.rs b/webauthn-authenticator-rs/src/transport/solokey.rs index fa63e0e3..60028736 100644 --- a/webauthn-authenticator-rs/src/transport/solokey.rs +++ b/webauthn-authenticator-rs/src/transport/solokey.rs @@ -41,9 +41,15 @@ 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; +#[cfg(all(feature = "usb", feature = "vendor-solokey"))] +pub const CMD_LOCK: u8 = super::TYPE_INIT | 0x63; + /// See [`SoloKeyAuthenticator`](crate::ctap2::SoloKeyAuthenticator). #[async_trait] pub trait SoloKeyToken { + /// See [`SoloKeyAuthenticator::get_solokey_lock()`](crate::ctap2::SoloKeyAuthenticator::get_solokey_lock). + async fn get_solokey_lock(&mut self) -> Result; + /// See [`SoloKeyAuthenticator::get_solokey_version()`](crate::ctap2::SoloKeyAuthenticator::get_solokey_version). async fn get_solokey_version(&mut self) -> Result; @@ -53,6 +59,18 @@ pub trait SoloKeyToken { #[async_trait] impl SoloKeyToken for AnyToken { + async fn get_solokey_lock(&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_lock().await, + } + } + async fn get_solokey_version(&mut self) -> Result { match self { AnyToken::Stub => unimplemented!(), diff --git a/webauthn-authenticator-rs/src/usb/solokey.rs b/webauthn-authenticator-rs/src/usb/solokey.rs index 1dc0f22f..d5f1697c 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, CMD_VERSION}, + solokey::{SoloKeyToken, CMD_LOCK, CMD_UUID, CMD_VERSION}, types::{U2FError, U2FHID_ERROR}, }, usb::framing::U2FHIDFrame, @@ -14,6 +14,31 @@ use super::{USBToken, USBTransport}; #[async_trait] impl SoloKeyToken for USBToken { + async fn get_solokey_lock(&mut self) -> Result { + let cmd = U2FHIDFrame { + cid: self.cid, + cmd: CMD_LOCK, + len: 0, + data: vec![], + }; + self.send_one(&cmd).await?; + + let r = self.recv_one().await?; + match r.cmd { + CMD_LOCK => { + if r.len != 1 || r.data.len() != 1 { + return Err(WebauthnCError::InvalidMessageLength); + } + + Ok(r.data[0] != 0) + } + + U2FHID_ERROR => Err(U2FError::from(r.data.as_slice()).into()), + + _ => Err(WebauthnCError::UnexpectedState), + } + } + async fn get_solokey_version(&mut self) -> Result { let cmd = U2FHIDFrame { cid: self.cid,