forked from kanidm/webauthn-rs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Event-based Transport/Token API / USB HID rewrite (kanidm#324)
* WIP: windows usb hid * basic windows HID works * break things * windows usb hotplug works! * cleanup error handlers * another stray error * guard USB support * document and tidy some things * add platforms * start macos support * start wrapping types * mac: hanging thread * mac: needs a little more wires... * event propagates * mac: basics working, next is cleanup * mac: unregister event handlers from correct thread * mac: add synthetic enumeration_completed event * mac: cleanup and improve tests * more cleaning * mac: more tidying, removed unneeded code... * mac: reorganising functions * mac: implement get_devices, remove debugging messages, more tidying * mac: move IOHIDManager methods into impl * mac: move methods into IOHIDHevice * mac: replace enums * mac: un-rename iohid symbols * remove unused import * start implementing linux * rewrite descriptor parser to iterator model * linux: update notes, remove unneeded code * linux: implement device discovery * linux: denoise * use kernel definitions for types * tidying, start rewriting fido-key-manager * clean up some types, document linux behaviour * nfc: start refactoring, add `info --watch` mode * nfc: less noise * fix up some NFC stuff, ignore silly readers * nfc: improve enumeration * start implementing bluetooth support again, fixing up a stupid macos PIV problem * macos: clean up a bunch of imports and usage of std::thread * delete a bunch of unused code * split platform-specific USB HID code into its own library (WIP) * tidy tidy * exclude fido-hid-rs from first doc pass * fixup linux issues, move errors into own file * more tidying * spawn_blocking on linux * cleanup mac stuff * clean up some dependency chains * add dbus * fix up clippy * fake bluetooth events * start migrating authenticate example * WIP: macos broken, refactoring... * refactor mac code to keep manager alive * cleanup more mac stuff * mac: delete more unneeded code * typo * fixup windows * fixup linux * docs updates, bluetooth tweaking * fixup linux crosscompile * linux again * more linux * Migrate NFC conformance test to new API * tidy up conformance tests * update cable_tunnel example for new API * start implementing selection, roll back Transport name changes * improve some windows things * document selection * type fun * migrate more comands * blah * start openssl notes * fido-key-manager: migrate fingerprints to new api * add remaining fido-key-manager commands * handle wine NFC stubs * quiet clippy * nfc stubs fix * format code, * fix up a bunch of broken * cleanup more lint issues
- Loading branch information
Showing
39 changed files
with
3,832 additions
and
779 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
[package] | ||
name = "fido-hid-rs" | ||
version = "0.5.0-dev" | ||
edition = "2021" | ||
rust-version = "1.66.0" | ||
license = "MPL-2.0" | ||
description = "USB HID library for FIDO authenticators" | ||
repository = "https://github.com/kanidm/webauthn-rs" | ||
|
||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
async-trait = "0.1.58" | ||
bitflags = "1.3.2" | ||
futures.workspace = true | ||
thiserror.workspace = true | ||
tokio.workspace = true | ||
tokio-stream.workspace = true | ||
tracing.workspace = true | ||
|
||
[build-dependencies] | ||
# Actually only required when targeting Linux, but Cargo doesn't support | ||
# build-dependencies for a single target, and you might be cross-compiling. | ||
bindgen = "0.65.1" | ||
|
||
[dev-dependencies] | ||
tracing-subscriber.workspace = true | ||
|
||
# descriptors tests | ||
hex.workspace = true | ||
num-derive = "0.3" | ||
num-traits = "0.2" | ||
|
||
[target.'cfg(target_os = "windows")'.dependencies] | ||
lazy_static = "1.4.0" | ||
windows = { version = "0.41.0", features = ["Win32_Foundation", "Win32_Devices_HumanInterfaceDevice", "Devices_Enumeration", "Devices_HumanInterfaceDevice", "Foundation", "Foundation_Collections", "Storage", "Storage_Streams" ] } | ||
|
||
[target.'cfg(target_os = "macos")'.dependencies] | ||
core-foundation = "0.9" | ||
libc = "0.2" | ||
mach2 = "0.4" | ||
|
||
[target.'cfg(target_os = "linux")'.dependencies] | ||
nix = { version = "0.26.2", features = ["ioctl", "poll"] } | ||
num-derive = "0.3" | ||
num-traits = "0.2" | ||
udev = "0.7.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# fido-hid-rs | ||
|
||
`fido-hid-rs` implements a minimal set of platform-specific USB HID bindings for | ||
communicating with FIDO authenticators. | ||
|
||
> **Important:** this library is an _internal implementation detail_ of | ||
> [webauthn-authenticator-rs][0] to work around Cargo limitations. | ||
> | ||
> **This library has no guarantees of API stability, and is not intended for use | ||
> by other parties.** | ||
> | ||
> If you want to interface with USB HID FIDO authenticators, use | ||
> [webauthn-authenticator-rs][0] instead of this library. | ||
> | ||
> If you're looking for a general-purpose Rust USB HID library, try [hidapi][]. | ||
This library currently targets (and is regularly tested on): | ||
|
||
* Linux on `x86_64` (target version TBC) | ||
* macOS 13 and later on `arm64` (Apple silicon) and `x86_64` | ||
* Windows 10 on `x86_64` and Windows 11 on `arm64` and `x86_64` | ||
|
||
We only test on the **current** service pack or point release of these operating | ||
systems. | ||
|
||
Other platforms (and older releases of these operating systems) are supported on | ||
a "passive" basis only: it might work, but we generally don't have the | ||
appropriate hardware available, and rely on users to notify us when things go | ||
wrong and provide patches! ♥️ | ||
|
||
[0]: ../webauthn-authenticator-rs | ||
[hidapi]: https://docs.rs/hidapi/latest/hidapi/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
use std::{env, path::PathBuf}; | ||
|
||
const LINUX_WRAPPER_H: &str = "src/linux/wrapper.h"; | ||
|
||
fn linux_headers() { | ||
println!("cargo:rerun-if-changed={LINUX_WRAPPER_H}"); | ||
let bindings = bindgen::builder() | ||
.header(LINUX_WRAPPER_H) | ||
.parse_callbacks(Box::new(bindgen::CargoCallbacks)) | ||
.derive_debug(false) | ||
.derive_default(true) | ||
.allowlist_type("hidraw_report_descriptor") | ||
.allowlist_type("hidraw_devinfo") | ||
.allowlist_var("HID_MAX_DESCRIPTOR_SIZE") | ||
.allowlist_var("BUS_(USB|BLUETOOTH|VIRTUAL)") | ||
.generate() | ||
.expect("unable to generate bindings"); | ||
|
||
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); | ||
bindings | ||
.write_to_file(out_path.join("linux_wrapper.rs")) | ||
.expect("Couldn't write bindings!"); | ||
} | ||
|
||
fn main() { | ||
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); | ||
|
||
if target_os == "linux" { | ||
linux_headers(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
//! USB HID report descriptor parser. | ||
//! | ||
//! ## References | ||
//! | ||
//! * [Device Class Definition for Human Interface Devices, version 1.11][0], | ||
//! §6.2.2 "Report Descriptor" | ||
//! | ||
//! [0]: https://www.usb.org/sites/default/files/documents/hid1_11.pdf | ||
use num_derive::FromPrimitive; | ||
use num_traits::FromPrimitive; | ||
|
||
use crate::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID}; | ||
|
||
/// HID descriptor tags; shifted right by 2 bits (removing the `bSize` field). | ||
#[derive(FromPrimitive)] | ||
#[repr(u8)] | ||
enum Tag { | ||
// Main items | ||
Input = 0b1000_00, | ||
Output = 0b1001_00, | ||
Feature = 0b1011_00, | ||
Collection = 0b1010_00, | ||
EndCollection = 0b1100_00, | ||
|
||
// Global items | ||
UsagePage = 0b0000_01, | ||
LogicalMinimum = 0b0001_01, | ||
LogicalMaximum = 0b0010_01, | ||
PhysicalMinimum = 0b0011_01, | ||
PhysicalMaximum = 0b0100_01, | ||
UnitExponent = 0b0101_01, | ||
Unit = 0b0110_01, | ||
ReportSize = 0b0111_01, | ||
ReportID = 0b1000_01, | ||
ReportCount = 0b1001_01, | ||
Push = 0b1010_01, | ||
Pop = 0b1011_01, | ||
|
||
// Local items | ||
Usage = 0b0000_10, | ||
UsageMinimum = 0b0001_10, | ||
UsageMaximum = 0b0010_10, | ||
DesignatorIndex = 0b0011_10, | ||
DesignatorMinimum = 0b0100_10, | ||
DesignatorMaximum = 0b0101_10, | ||
StringIndex = 0b0111_10, | ||
StringMinimum = 0b1000_10, | ||
StringMaximum = 0b1001_10, | ||
Delimiter = 0b1010_10, | ||
} | ||
|
||
/// Item in a report descriptor. | ||
struct DescriptorItem<'a> { | ||
/// The tag of the item, if known. | ||
tag: Option<Tag>, | ||
|
||
/// The value of the item. | ||
value: &'a [u8], | ||
} | ||
|
||
/// Iterator-based report descriptor parser. | ||
/// | ||
/// This returns each [item] in a descriptor, without regard to its context. | ||
/// | ||
/// ## Limitations | ||
/// | ||
/// This only fully supports short items. This will parse long items, but skip | ||
/// the tag. | ||
/// | ||
/// [item]: DescriptorItem | ||
struct DescriptorIterator<'a> { | ||
i: &'a [u8], | ||
} | ||
|
||
impl<'a> Iterator for DescriptorIterator<'a> { | ||
type Item = DescriptorItem<'a>; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
if self.i.is_empty() { | ||
return None; | ||
} | ||
|
||
let tag; | ||
let value; | ||
|
||
let mut i0 = self.i[0]; | ||
if i0 == 0xfe { | ||
// Long item: 0xfe [size] [tag] [data...] | ||
if self.i.len() < 3 { | ||
// Not enough bytes to get the long item length and tag | ||
return None; | ||
} | ||
|
||
let length = usize::from(self.i[1]); | ||
if self.i.len() < length + 3 { | ||
// Not enough bytes to get long item value | ||
return None; | ||
} | ||
warn!("long tags are not supported"); | ||
tag = None; | ||
(value, self.i) = self.i[3..].split_at(length); | ||
} else { | ||
// Short item: [tag | type | size] [data...] | ||
let mut length = usize::from(i0 & 0x03); | ||
if length == 0x03 { | ||
length += 1; | ||
} | ||
i0 >>= 2; | ||
|
||
tag = Tag::from_u8(i0); | ||
// if tag.is_none() { | ||
// warn!("unknown short tag: 0b{i0:b}",); | ||
// } | ||
if self.i.len() < length + 1 { | ||
// Not enough bytes to get short item value | ||
return None; | ||
} | ||
(value, self.i) = self.i[1..].split_at(length); | ||
} | ||
|
||
Some(DescriptorItem { tag, value }) | ||
} | ||
} | ||
|
||
/// Parses a USB HID Report Descriptor to determine whether it is a FIDO | ||
/// authenticator. | ||
/// | ||
/// This only handles [`Tag::UsagePage`] and [`Tag::Usage`], so will only | ||
/// support simple descriptors. This is should be sufficient for USB FIDO | ||
/// authethicators. | ||
pub fn is_fido_authenticator(descriptor: &[u8]) -> bool { | ||
let mut descriptor = DescriptorIterator { i: descriptor }; | ||
let mut current_usage_page = 0u16; | ||
while let Some(item) = descriptor.next() { | ||
// trace!("item: {item:?}"); | ||
match item.tag { | ||
Some(Tag::UsagePage) => { | ||
if let Ok(usage_page) = item.value.try_into() { | ||
current_usage_page = u16::from_le_bytes(usage_page); | ||
} | ||
} | ||
|
||
Some(Tag::Usage) => { | ||
if current_usage_page == FIDO_USAGE_PAGE { | ||
// 1 or 2 byte usage page; expect the current usage page to be FIDO | ||
if item.value.len() == 1 && u16::from(item.value[0]) == FIDO_USAGE_U2FHID { | ||
return true; | ||
} | ||
|
||
if let Ok(usage) = item.value.try_into() { | ||
if u16::from_le_bytes(usage) == FIDO_USAGE_U2FHID { | ||
return true; | ||
} | ||
} | ||
} | ||
|
||
if let Ok(usage) = item.value.try_into() { | ||
// 4 byte usage page; doesn't matter what the current usage page is | ||
let usage_and_page = u32::from_le_bytes(usage); | ||
let usage_page = (usage_and_page >> 16) as u16; | ||
let usage = (usage_and_page & 0xffff) as u16; | ||
|
||
if usage_page == FIDO_USAGE_PAGE && usage == FIDO_USAGE_U2FHID { | ||
return true; | ||
} | ||
} | ||
} | ||
|
||
_ => continue, | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
macro_rules! fido_descriptor_tests { | ||
($($name:ident: $expected:expr, $value:expr;)*) => { | ||
$( | ||
#[test] | ||
fn $name() -> std::result::Result<(), Box<dyn std::error::Error>> { | ||
let _ = tracing_subscriber::fmt().try_init(); | ||
|
||
let descriptor = hex::decode($value)?; | ||
assert_eq!($expected, is_fido_authenticator(&descriptor)); | ||
Ok(()) | ||
} | ||
)* | ||
} | ||
} | ||
|
||
fido_descriptor_tests! { | ||
// Used by Feitian and Token2; 2 byte usage page + 1 byte usage | ||
feitian_token2: true, "06d0f10901a1010920150026ff007508954081020921150026ff00750895409102c0"; | ||
|
||
feitian_otp: false, "05010906a101050719e029e71500250175019508810295017508810395057501050819012905910295017503910395067508150025650507190029658100090375089540b102c0"; | ||
keyboard1: false, "05010906a101050719e029e71500250175019508810295017508810195037501050819012903910295057501910195067508150026ff00050719002aff008100c0"; | ||
keyboard2: false, "0601000980a10185011981298315002501950375018102950175058101c0050c0901a1018503150025017501950819b529b809cd09e209e909ea81020a83010a8a010a92010a94010a21021a23022a250281020a26020a27020a2a029503810295058101c00600ff0901a1018502250115007501950b1af1002afb008102950175058101150026ff7f092075109501b102c0"; | ||
mouse1: false, "05010902a1010901a100050919012908150025019508750181020600ff0940950275081581257f810205010938950181060930093116018026ff7f751095028106c0c0"; | ||
mouse2: false, "0680ff0980a10185801a00382a0738150025019508750181028520092095017508b102858e098eb102c0"; | ||
|
||
invalid_1_byte: false, "01"; | ||
invalid_2_byte: false, "0201"; | ||
invalid_4_byte: false, "02010203"; | ||
invalid_long: false, "fe"; | ||
invalid_long_usage_page: false, "0701020304"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
use thiserror::Error; | ||
|
||
pub type Result<T> = std::result::Result<T, HidError>; | ||
|
||
#[derive(Debug, Error, PartialEq, Eq, PartialOrd, Ord)] | ||
pub enum HidError { | ||
#[error("I/O error communicating with device: {0}")] | ||
IoError(String), | ||
#[error("internal error, likely library bug")] | ||
Internal, | ||
#[error("attempted to communicate with a closed device")] | ||
Closed, | ||
#[error("device sent an unexpected message length")] | ||
InvalidMessageLength, | ||
#[error("could not send data to device")] | ||
SendError, | ||
#[error("permission denied")] | ||
PermissionDenied, | ||
} | ||
|
||
impl From<std::io::Error> for HidError { | ||
fn from(v: std::io::Error) -> Self { | ||
Self::IoError(v.to_string()) | ||
} | ||
} |
Oops, something went wrong.