From b910768b1ea33e9df3020366c520b0de361e4557 Mon Sep 17 00:00:00 2001 From: Michael Farrell Date: Thu, 26 Oct 2023 13:08:56 +1000 Subject: [PATCH] WIP: implement yubikey config --- fido-key-manager/Cargo.toml | 1 + fido-key-manager/src/main.rs | 18 ++++++++ webauthn-authenticator-rs/Cargo.toml | 2 + webauthn-authenticator-rs/src/ctap2/ctap20.rs | 5 ++- webauthn-authenticator-rs/src/ctap2/mod.rs | 7 +++ .../src/ctap2/yubikey.rs | 34 ++++++++++++++ .../src/transport/mod.rs | 2 + .../src/transport/yubikey.rs | 45 +++++++++++++++++++ webauthn-authenticator-rs/src/usb/mod.rs | 2 + webauthn-authenticator-rs/src/usb/yubikey.rs | 44 ++++++++++++++++++ 10 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 webauthn-authenticator-rs/src/ctap2/yubikey.rs create mode 100644 webauthn-authenticator-rs/src/transport/yubikey.rs create mode 100644 webauthn-authenticator-rs/src/usb/yubikey.rs diff --git a/fido-key-manager/Cargo.toml b/fido-key-manager/Cargo.toml index 202783c3..d4d84135 100644 --- a/fido-key-manager/Cargo.toml +++ b/fido-key-manager/Cargo.toml @@ -24,6 +24,7 @@ bluetooth = ["webauthn-authenticator-rs/bluetooth"] nfc = ["webauthn-authenticator-rs/nfc"] usb = ["webauthn-authenticator-rs/usb"] solokey = ["webauthn-authenticator-rs/vendor-solokey"] +yubikey = ["webauthn-authenticator-rs/vendor-yubikey"] default = ["nfc", "usb"] diff --git a/fido-key-manager/src/main.rs b/fido-key-manager/src/main.rs index d8f41276..bfa3dd55 100644 --- a/fido-key-manager/src/main.rs +++ b/fido-key-manager/src/main.rs @@ -13,6 +13,8 @@ use std::time::Duration; use tokio_stream::StreamExt; #[cfg(feature = "solokey")] use webauthn_authenticator_rs::ctap2::SoloKeyAuthenticator; +#[cfg(feature = "yubikey")] +use webauthn_authenticator_rs::ctap2::YubiKeyAuthenticator; use webauthn_authenticator_rs::prelude::WebauthnCError; use webauthn_authenticator_rs::{ ctap2::{ @@ -206,6 +208,8 @@ pub enum Opt { #[cfg(feature = "solokey")] /// Gets some random bytes from a connected SoloKey 2 or Trussed device. SoloKeyRandom, + #[cfg(feature = "yubikey")] + YubikeyGetConfig, } #[derive(Debug, clap::Parser)] @@ -770,5 +774,19 @@ async fn main() { .expect("Error getting random data"); println!("Random bytes: {}", hex::encode(r)); } + + #[cfg(feature = "yubikey")] + Opt::YubikeyGetConfig => { + // TODO: filter this to just YubiKey devices in a safe way + println!("Insert a YubiKey device..."); + let mut token: CtapAuthenticator = + select_one_device(stream, &ui).await.unwrap(); + + let r = token + .get_yubikey_config() + .await + .expect("Error getting random data"); + todo!() + } } } diff --git a/webauthn-authenticator-rs/Cargo.toml b/webauthn-authenticator-rs/Cargo.toml index 6bfb2d06..8bf651f7 100644 --- a/webauthn-authenticator-rs/Cargo.toml +++ b/webauthn-authenticator-rs/Cargo.toml @@ -52,6 +52,8 @@ ctap2 = [ ctap2-management = ["ctap2"] # Support for SoloKey's vendor commands vendor-solokey = [] +# Support for YubiKey's vendor commands +vendor-yubikey = [] nfc = ["ctap2", "dep:pcsc"] # TODO: allow running softpasskey without softtoken softpasskey = ["crypto", "softtoken"] diff --git a/webauthn-authenticator-rs/src/ctap2/ctap20.rs b/webauthn-authenticator-rs/src/ctap2/ctap20.rs index 45f45fa5..4e4cdb06 100644 --- a/webauthn-authenticator-rs/src/ctap2/ctap20.rs +++ b/webauthn-authenticator-rs/src/ctap2/ctap20.rs @@ -533,7 +533,10 @@ impl<'a, T: Token, U: UiCallback> Ctap20Authenticator<'a, T, U> { let ret = self.token.transmit(mc, self.ui_callback).await; if let Err(WebauthnCError::Ctap(e)) = ret { - if e == CtapError::Ctap2PinAuthInvalid || e == CtapError::Ctap2PinNotSet { + if e == CtapError::Ctap2PinAuthInvalid + || e == CtapError::Ctap2PinNotSet + || e == CtapError::Ctap2PinInvalid + { // User pressed the button return Ok(()); } diff --git a/webauthn-authenticator-rs/src/ctap2/mod.rs b/webauthn-authenticator-rs/src/ctap2/mod.rs index ac45ea49..6d36f4da 100644 --- a/webauthn-authenticator-rs/src/ctap2/mod.rs +++ b/webauthn-authenticator-rs/src/ctap2/mod.rs @@ -131,6 +131,9 @@ mod pin_uv; #[cfg(any(all(doc, not(doctest)), feature = "vendor-solokey"))] #[doc(hidden)] mod solokey; +#[cfg(any(all(doc, not(doctest)), feature = "vendor-yubikey"))] +#[doc(hidden)] +mod yubikey; use std::ops::{Deref, DerefMut}; use std::pin::Pin; @@ -166,6 +169,10 @@ pub use self::{ #[doc(inline)] pub use self::solokey::SoloKeyAuthenticator; +#[cfg(any(all(doc, not(doctest)), feature = "vendor-yubikey"))] +#[doc(inline)] +pub use self::yubikey::YubiKeyAuthenticator; + /// Abstraction for different versions of the CTAP2 protocol. /// /// All tokens can [Deref] into [Ctap20Authenticator]. diff --git a/webauthn-authenticator-rs/src/ctap2/yubikey.rs b/webauthn-authenticator-rs/src/ctap2/yubikey.rs new file mode 100644 index 00000000..ce24ec7c --- /dev/null +++ b/webauthn-authenticator-rs/src/ctap2/yubikey.rs @@ -0,0 +1,34 @@ +use async_trait::async_trait; + +use crate::{ + prelude::WebauthnCError, + transport::{yubikey::YubiKeyToken, Token}, + ui::UiCallback, +}; + +use super::Ctap20Authenticator; + +/// YubiKey vendor-specific commands. +/// +/// ## Warning +/// +/// These commands currently operate on *any* [`Ctap20Authenticator`][], and do +/// not filter to just YubiKey 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 TODO +#[async_trait] +pub trait YubiKeyAuthenticator { + async fn get_yubikey_config(&mut self) -> Result; +} + +#[async_trait] +impl<'a, T: Token + YubiKeyToken, U: UiCallback> YubiKeyAuthenticator + for Ctap20Authenticator<'a, T, U> +{ + #[inline] + async fn get_yubikey_config(&mut self) -> Result { + self.token.get_yubikey_config().await + } +} diff --git a/webauthn-authenticator-rs/src/transport/mod.rs b/webauthn-authenticator-rs/src/transport/mod.rs index c1d8a3d4..c6d4d268 100644 --- a/webauthn-authenticator-rs/src/transport/mod.rs +++ b/webauthn-authenticator-rs/src/transport/mod.rs @@ -7,6 +7,8 @@ pub mod iso7816; pub(crate) mod solokey; #[cfg(any(doc, feature = "bluetooth", feature = "usb"))] pub(crate) mod types; +#[cfg(any(all(doc, not(doctest)), feature = "vendor-yubikey"))] +pub(crate) mod yubikey; pub use crate::transport::any::{AnyToken, AnyTransport}; diff --git a/webauthn-authenticator-rs/src/transport/yubikey.rs b/webauthn-authenticator-rs/src/transport/yubikey.rs new file mode 100644 index 00000000..7760dfe3 --- /dev/null +++ b/webauthn-authenticator-rs/src/transport/yubikey.rs @@ -0,0 +1,45 @@ +//! YubiKey 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 +//! ------- | ----------- | ------- | -------- +//! +//! ## NFC +//! +//! ## References +//! +use async_trait::async_trait; + +use crate::prelude::WebauthnCError; + +use super::AnyToken; + +#[cfg(all(feature = "usb", feature = "vendor-yubikey"))] +pub(crate) const CMD_GET_CONFIG: u8 = super::TYPE_INIT | 0x42; + +/// See [`YubiKeyAuthenticator`](crate::ctap2::YubiKeyAuthenticator). +#[async_trait] +pub trait YubiKeyToken { + /// See [`SoloKeyAuthenticator::get_solokey_lock()`](crate::ctap2::SoloKeyAuthenticator::get_solokey_lock). + async fn get_yubikey_config(&mut self) -> Result; +} + +#[async_trait] +#[allow(clippy::unimplemented)] +impl YubiKeyToken for AnyToken { + async fn get_yubikey_config(&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_yubikey_config().await, + } + } +} diff --git a/webauthn-authenticator-rs/src/usb/mod.rs b/webauthn-authenticator-rs/src/usb/mod.rs index 8fdf2388..63b202b4 100644 --- a/webauthn-authenticator-rs/src/usb/mod.rs +++ b/webauthn-authenticator-rs/src/usb/mod.rs @@ -15,6 +15,8 @@ mod framing; mod responses; #[cfg(any(all(doc, not(doctest)), feature = "vendor-solokey"))] mod solokey; +#[cfg(any(all(doc, not(doctest)), feature = "vendor-yubikey"))] +mod yubikey; use fido_hid_rs::{ HidReportBytes, HidSendReportBytes, USBDevice, USBDeviceImpl, USBDeviceInfo, USBDeviceInfoImpl, diff --git a/webauthn-authenticator-rs/src/usb/yubikey.rs b/webauthn-authenticator-rs/src/usb/yubikey.rs new file mode 100644 index 00000000..8a75adcf --- /dev/null +++ b/webauthn-authenticator-rs/src/usb/yubikey.rs @@ -0,0 +1,44 @@ +use async_trait::async_trait; +use uuid::Uuid; + +#[cfg(all(feature = "usb", feature = "vendor-yubikey"))] +use crate::transport::yubikey::CMD_GET_CONFIG; + +use crate::{ + prelude::WebauthnCError, + transport::{ + types::{U2FError, U2FHID_ERROR}, + yubikey::YubiKeyToken, + }, + usb::{framing::U2FHIDFrame, USBToken}, +}; + +#[async_trait] +impl YubiKeyToken for USBToken { + async fn get_yubikey_config(&mut self) -> Result { + let cmd = U2FHIDFrame { + cid: self.cid, + cmd: CMD_GET_CONFIG, + len: 0, + data: vec![], + }; + self.send_one(&cmd).await?; + + let r = self.recv_one().await?; + debug!("config: {}", hex::encode(r.data)); + todo!() + // 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), + // } + } +}