Skip to content

Commit

Permalink
add solo-key-version, docs
Browse files Browse the repository at this point in the history
  • Loading branch information
micolous committed Oct 25, 2023
1 parent 54b5de7 commit da72ad5
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 10 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ tokio = { version = "1.22.0", features = [
"sync",
"test-util",
"macros",
"net",
"rt-multi-thread",
"time",
] }
Expand Down
37 changes: 36 additions & 1 deletion fido-key-manager/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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) => {
Expand All @@ -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;
}
}
_ => (),
}
}
}
}
}
4 changes: 2 additions & 2 deletions webauthn-authenticator-rs/src/bluetooth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
24 changes: 22 additions & 2 deletions webauthn-authenticator-rs/src/ctap2/solokey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Uuid, WebauthnCError>;
/// Gets the device-specific UUID of a SoloKey token.
async fn get_solokey_uuid(&mut self) -> Result<Uuid, WebauthnCError>;

/// Gets the version of a SoloKey token.
async fn get_solokey_version(&mut self) -> Result<u32, WebauthnCError>;
}

#[async_trait]
impl<'a, T: Token + SoloKeyToken, U: UiCallback> SoloKeyAuthenticator
for Ctap20Authenticator<'a, T, U>
{
async fn get_uuid(&mut self) -> Result<Uuid, WebauthnCError> {
#[inline]
async fn get_solokey_uuid(&mut self) -> Result<Uuid, WebauthnCError> {
self.token.get_solokey_uuid().await
}

#[inline]
async fn get_solokey_version(&mut self) -> Result<u32, WebauthnCError> {
self.token.get_solokey_version().await
}
}
50 changes: 50 additions & 0 deletions webauthn-authenticator-rs/src/transport/solokey.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,70 @@
//! 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;

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<u32, WebauthnCError>;

/// See [`SoloKeyAuthenticator::get_solokey_uuid()`](crate::ctap2::SoloKeyAuthenticator::get_solokey_uuid).
async fn get_solokey_uuid(&mut self) -> Result<Uuid, WebauthnCError>;
}

#[async_trait]
impl SoloKeyToken for AnyToken {
async fn get_solokey_version(&mut self) -> Result<u32, WebauthnCError> {
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<Uuid, WebauthnCError> {
match self {
AnyToken::Stub => unimplemented!(),
Expand Down
1 change: 1 addition & 0 deletions webauthn-authenticator-rs/src/transport/types.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::TYPE_INIT;
use crate::error::{CtapError, WebauthnCError};

#[cfg(any(all(doc, not(doctest)), feature = "usb"))]
Expand Down
11 changes: 7 additions & 4 deletions webauthn-authenticator-rs/src/usb/responses.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down
29 changes: 28 additions & 1 deletion webauthn-authenticator-rs/src/usb/solokey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -14,6 +14,33 @@ use super::{USBToken, USBTransport};

#[async_trait]
impl SoloKeyToken for USBToken {
async fn get_solokey_version(&mut self) -> Result<u32, WebauthnCError> {
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<Uuid, WebauthnCError> {
let cmd = U2FHIDFrame {
cid: self.cid,
Expand Down

0 comments on commit da72ad5

Please sign in to comment.