From 933eaef6c7430bdd1aaa5e44915d5f6a25cd8ded Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus <> Date: Tue, 4 May 2021 10:48:10 +0200 Subject: [PATCH 1/7] Moving platform specific modules into new module 'transport' --- src/lib.rs | 39 +------------------ src/statemachine.rs | 3 +- src/{ => transport}/freebsd/device.rs | 2 +- src/{ => transport}/freebsd/mod.rs | 0 src/{ => transport}/freebsd/monitor.rs | 0 src/{ => transport}/freebsd/transaction.rs | 2 +- src/{ => transport}/freebsd/uhid.rs | 0 src/{ => transport}/hidproto.rs | 0 src/{ => transport}/linux/device.rs | 2 +- src/{ => transport}/linux/hidraw.rs | 2 +- src/{ => transport}/linux/hidwrapper.h | 0 src/{ => transport}/linux/hidwrapper.rs | 0 src/{ => transport}/linux/ioctl_aarch64le.rs | 0 src/{ => transport}/linux/ioctl_armle.rs | 0 src/{ => transport}/linux/ioctl_mips64le.rs | 0 src/{ => transport}/linux/ioctl_mipsbe.rs | 0 src/{ => transport}/linux/ioctl_mipsle.rs | 0 .../linux/ioctl_powerpc64be.rs | 0 .../linux/ioctl_powerpc64le.rs | 0 src/{ => transport}/linux/ioctl_powerpcbe.rs | 0 src/{ => transport}/linux/ioctl_s390xbe.rs | 0 src/{ => transport}/linux/ioctl_x86.rs | 0 src/{ => transport}/linux/ioctl_x86_64.rs | 0 src/{ => transport}/linux/mod.rs | 0 src/{ => transport}/linux/monitor.rs | 0 src/{ => transport}/linux/transaction.rs | 2 +- src/{ => transport}/macos/device.rs | 2 +- src/{ => transport}/macos/iokit.rs | 0 src/{ => transport}/macos/mod.rs | 0 src/{ => transport}/macos/monitor.rs | 2 +- src/{ => transport}/macos/transaction.rs | 4 +- src/transport/mod.rs | 38 ++++++++++++++++++ src/{ => transport}/netbsd/device.rs | 4 +- src/{ => transport}/netbsd/fd.rs | 0 src/{ => transport}/netbsd/mod.rs | 0 src/{ => transport}/netbsd/monitor.rs | 2 +- src/{ => transport}/netbsd/transaction.rs | 4 +- src/{ => transport}/netbsd/uhid.rs | 2 +- src/{ => transport}/openbsd/device.rs | 2 +- src/{ => transport}/openbsd/mod.rs | 0 src/{ => transport}/openbsd/monitor.rs | 0 src/{ => transport}/openbsd/transaction.rs | 2 +- src/{ => transport}/stub/device.rs | 0 src/{ => transport}/stub/mod.rs | 0 src/{ => transport}/stub/transaction.rs | 0 src/{ => transport}/windows/device.rs | 0 src/{ => transport}/windows/mod.rs | 0 src/{ => transport}/windows/monitor.rs | 2 +- src/{ => transport}/windows/transaction.rs | 2 +- src/{ => transport}/windows/winapi.rs | 6 +-- 50 files changed, 62 insertions(+), 62 deletions(-) rename src/{ => transport}/freebsd/device.rs (98%) rename src/{ => transport}/freebsd/mod.rs (100%) rename src/{ => transport}/freebsd/monitor.rs (100%) rename src/{ => transport}/freebsd/transaction.rs (97%) rename src/{ => transport}/freebsd/uhid.rs (100%) rename src/{ => transport}/hidproto.rs (100%) rename src/{ => transport}/linux/device.rs (98%) rename src/{ => transport}/linux/hidraw.rs (98%) rename src/{ => transport}/linux/hidwrapper.h (100%) rename src/{ => transport}/linux/hidwrapper.rs (100%) rename src/{ => transport}/linux/ioctl_aarch64le.rs (100%) rename src/{ => transport}/linux/ioctl_armle.rs (100%) rename src/{ => transport}/linux/ioctl_mips64le.rs (100%) rename src/{ => transport}/linux/ioctl_mipsbe.rs (100%) rename src/{ => transport}/linux/ioctl_mipsle.rs (100%) rename src/{ => transport}/linux/ioctl_powerpc64be.rs (100%) rename src/{ => transport}/linux/ioctl_powerpc64le.rs (100%) rename src/{ => transport}/linux/ioctl_powerpcbe.rs (100%) rename src/{ => transport}/linux/ioctl_s390xbe.rs (100%) rename src/{ => transport}/linux/ioctl_x86.rs (100%) rename src/{ => transport}/linux/ioctl_x86_64.rs (100%) rename src/{ => transport}/linux/mod.rs (100%) rename src/{ => transport}/linux/monitor.rs (100%) rename src/{ => transport}/linux/transaction.rs (96%) rename src/{ => transport}/macos/device.rs (99%) rename src/{ => transport}/macos/iokit.rs (100%) rename src/{ => transport}/macos/mod.rs (100%) rename src/{ => transport}/macos/monitor.rs (99%) rename src/{ => transport}/macos/transaction.rs (95%) create mode 100644 src/transport/mod.rs rename src/{ => transport}/netbsd/device.rs (98%) rename src/{ => transport}/netbsd/fd.rs (100%) rename src/{ => transport}/netbsd/mod.rs (100%) rename src/{ => transport}/netbsd/monitor.rs (98%) rename src/{ => transport}/netbsd/transaction.rs (94%) rename src/{ => transport}/netbsd/uhid.rs (98%) rename src/{ => transport}/openbsd/device.rs (98%) rename src/{ => transport}/openbsd/mod.rs (100%) rename src/{ => transport}/openbsd/monitor.rs (100%) rename src/{ => transport}/openbsd/transaction.rs (96%) rename src/{ => transport}/stub/device.rs (100%) rename src/{ => transport}/stub/mod.rs (100%) rename src/{ => transport}/stub/transaction.rs (100%) rename src/{ => transport}/windows/device.rs (100%) rename src/{ => transport}/windows/mod.rs (100%) rename src/{ => transport}/windows/monitor.rs (97%) rename src/{ => transport}/windows/transaction.rs (96%) rename src/{ => transport}/windows/winapi.rs (96%) diff --git a/src/lib.rs b/src/lib.rs index cfe82deb..1f660fa5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,53 +5,15 @@ #[macro_use] mod util; -#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] -pub mod hidproto; - #[cfg(any(target_os = "linux"))] extern crate libudev; -#[cfg(any(target_os = "linux"))] -#[path = "linux/mod.rs"] -pub mod platform; - #[cfg(any(target_os = "freebsd"))] extern crate devd_rs; -#[cfg(any(target_os = "freebsd"))] -#[path = "freebsd/mod.rs"] -pub mod platform; - -#[cfg(any(target_os = "netbsd"))] -#[path = "netbsd/mod.rs"] -pub mod platform; - -#[cfg(any(target_os = "openbsd"))] -#[path = "openbsd/mod.rs"] -pub mod platform; - #[cfg(any(target_os = "macos"))] extern crate core_foundation; -#[cfg(any(target_os = "macos"))] -#[path = "macos/mod.rs"] -pub mod platform; - -#[cfg(any(target_os = "windows"))] -#[path = "windows/mod.rs"] -pub mod platform; - -#[cfg(not(any( - target_os = "linux", - target_os = "freebsd", - target_os = "openbsd", - target_os = "netbsd", - target_os = "macos", - target_os = "windows" -)))] -#[path = "stub/mod.rs"] -pub mod platform; - extern crate libc; #[macro_use] extern crate log; @@ -76,6 +38,7 @@ pub use crate::capi::*; pub mod errors; pub mod statecallback; mod virtualdevices; +mod transport; // Keep this in sync with the constants in u2fhid-capi.h. bitflags! { diff --git a/src/statemachine.rs b/src/statemachine.rs index 32552f8d..4ef98632 100644 --- a/src/statemachine.rs +++ b/src/statemachine.rs @@ -4,8 +4,7 @@ use crate::consts::PARAMETER_SIZE; use crate::errors; -use crate::platform::device::Device; -use crate::platform::transaction::Transaction; +use crate::transport::platform::{device::Device, transaction::Transaction}; use crate::statecallback::StateCallback; use crate::u2fprotocol::{u2f_init_device, u2f_is_keyhandle_valid, u2f_register, u2f_sign}; use crate::u2ftypes::U2FDevice; diff --git a/src/freebsd/device.rs b/src/transport/freebsd/device.rs similarity index 98% rename from src/freebsd/device.rs rename to src/transport/freebsd/device.rs index 32069cd5..fb60deaf 100644 --- a/src/freebsd/device.rs +++ b/src/transport/freebsd/device.rs @@ -10,7 +10,7 @@ use std::io::{Read, Write}; use std::os::unix::prelude::*; use crate::consts::{CID_BROADCAST, MAX_HID_RPT_SIZE}; -use crate::platform::uhid; +use crate::transport::platform::uhid; use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; use crate::util::from_unix_result; diff --git a/src/freebsd/mod.rs b/src/transport/freebsd/mod.rs similarity index 100% rename from src/freebsd/mod.rs rename to src/transport/freebsd/mod.rs diff --git a/src/freebsd/monitor.rs b/src/transport/freebsd/monitor.rs similarity index 100% rename from src/freebsd/monitor.rs rename to src/transport/freebsd/monitor.rs diff --git a/src/freebsd/transaction.rs b/src/transport/freebsd/transaction.rs similarity index 97% rename from src/freebsd/transaction.rs rename to src/transport/freebsd/transaction.rs index e7cd00f1..67935ace 100644 --- a/src/freebsd/transaction.rs +++ b/src/transport/freebsd/transaction.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::errors; -use crate::platform::monitor::Monitor; +use crate::transport::platformmonitor::Monitor; use crate::statecallback::StateCallback; use runloop::RunLoop; use std::ffi::OsString; diff --git a/src/freebsd/uhid.rs b/src/transport/freebsd/uhid.rs similarity index 100% rename from src/freebsd/uhid.rs rename to src/transport/freebsd/uhid.rs diff --git a/src/hidproto.rs b/src/transport/hidproto.rs similarity index 100% rename from src/hidproto.rs rename to src/transport/hidproto.rs diff --git a/src/linux/device.rs b/src/transport/linux/device.rs similarity index 98% rename from src/linux/device.rs rename to src/transport/linux/device.rs index 4a0d58d6..cb58ab5d 100644 --- a/src/linux/device.rs +++ b/src/transport/linux/device.rs @@ -10,7 +10,7 @@ use std::io::{Read, Write}; use std::os::unix::prelude::*; use crate::consts::CID_BROADCAST; -use crate::platform::{hidraw, monitor}; +use crate::transport::platform::{hidraw, monitor}; use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; use crate::util::from_unix_result; diff --git a/src/linux/hidraw.rs b/src/transport/linux/hidraw.rs similarity index 98% rename from src/linux/hidraw.rs rename to src/transport/linux/hidraw.rs index 662ea832..16d687f3 100644 --- a/src/linux/hidraw.rs +++ b/src/transport/linux/hidraw.rs @@ -10,7 +10,7 @@ use std::os::unix::io::RawFd; use super::hidwrapper::{_HIDIOCGRDESC, _HIDIOCGRDESCSIZE}; use crate::consts::MAX_HID_RPT_SIZE; -use crate::hidproto::*; +use crate::transport::hidproto::*; use crate::util::{from_unix_result, io_err}; #[allow(non_camel_case_types)] diff --git a/src/linux/hidwrapper.h b/src/transport/linux/hidwrapper.h similarity index 100% rename from src/linux/hidwrapper.h rename to src/transport/linux/hidwrapper.h diff --git a/src/linux/hidwrapper.rs b/src/transport/linux/hidwrapper.rs similarity index 100% rename from src/linux/hidwrapper.rs rename to src/transport/linux/hidwrapper.rs diff --git a/src/linux/ioctl_aarch64le.rs b/src/transport/linux/ioctl_aarch64le.rs similarity index 100% rename from src/linux/ioctl_aarch64le.rs rename to src/transport/linux/ioctl_aarch64le.rs diff --git a/src/linux/ioctl_armle.rs b/src/transport/linux/ioctl_armle.rs similarity index 100% rename from src/linux/ioctl_armle.rs rename to src/transport/linux/ioctl_armle.rs diff --git a/src/linux/ioctl_mips64le.rs b/src/transport/linux/ioctl_mips64le.rs similarity index 100% rename from src/linux/ioctl_mips64le.rs rename to src/transport/linux/ioctl_mips64le.rs diff --git a/src/linux/ioctl_mipsbe.rs b/src/transport/linux/ioctl_mipsbe.rs similarity index 100% rename from src/linux/ioctl_mipsbe.rs rename to src/transport/linux/ioctl_mipsbe.rs diff --git a/src/linux/ioctl_mipsle.rs b/src/transport/linux/ioctl_mipsle.rs similarity index 100% rename from src/linux/ioctl_mipsle.rs rename to src/transport/linux/ioctl_mipsle.rs diff --git a/src/linux/ioctl_powerpc64be.rs b/src/transport/linux/ioctl_powerpc64be.rs similarity index 100% rename from src/linux/ioctl_powerpc64be.rs rename to src/transport/linux/ioctl_powerpc64be.rs diff --git a/src/linux/ioctl_powerpc64le.rs b/src/transport/linux/ioctl_powerpc64le.rs similarity index 100% rename from src/linux/ioctl_powerpc64le.rs rename to src/transport/linux/ioctl_powerpc64le.rs diff --git a/src/linux/ioctl_powerpcbe.rs b/src/transport/linux/ioctl_powerpcbe.rs similarity index 100% rename from src/linux/ioctl_powerpcbe.rs rename to src/transport/linux/ioctl_powerpcbe.rs diff --git a/src/linux/ioctl_s390xbe.rs b/src/transport/linux/ioctl_s390xbe.rs similarity index 100% rename from src/linux/ioctl_s390xbe.rs rename to src/transport/linux/ioctl_s390xbe.rs diff --git a/src/linux/ioctl_x86.rs b/src/transport/linux/ioctl_x86.rs similarity index 100% rename from src/linux/ioctl_x86.rs rename to src/transport/linux/ioctl_x86.rs diff --git a/src/linux/ioctl_x86_64.rs b/src/transport/linux/ioctl_x86_64.rs similarity index 100% rename from src/linux/ioctl_x86_64.rs rename to src/transport/linux/ioctl_x86_64.rs diff --git a/src/linux/mod.rs b/src/transport/linux/mod.rs similarity index 100% rename from src/linux/mod.rs rename to src/transport/linux/mod.rs diff --git a/src/linux/monitor.rs b/src/transport/linux/monitor.rs similarity index 100% rename from src/linux/monitor.rs rename to src/transport/linux/monitor.rs diff --git a/src/linux/transaction.rs b/src/transport/linux/transaction.rs similarity index 96% rename from src/linux/transaction.rs rename to src/transport/linux/transaction.rs index e7cd00f1..41742b8e 100644 --- a/src/linux/transaction.rs +++ b/src/transport/linux/transaction.rs @@ -3,8 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::errors; -use crate::platform::monitor::Monitor; use crate::statecallback::StateCallback; +use crate::transport::platform::monitor::Monitor; use runloop::RunLoop; use std::ffi::OsString; diff --git a/src/macos/device.rs b/src/transport/macos/device.rs similarity index 99% rename from src/macos/device.rs rename to src/transport/macos/device.rs index 425a2795..939a1c6d 100644 --- a/src/macos/device.rs +++ b/src/transport/macos/device.rs @@ -5,7 +5,7 @@ extern crate log; use crate::consts::{CID_BROADCAST, MAX_HID_RPT_SIZE}; -use crate::platform::iokit::*; +use crate::transport::platform::iokit::*; use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; use core_foundation::base::*; use core_foundation::string::*; diff --git a/src/macos/iokit.rs b/src/transport/macos/iokit.rs similarity index 100% rename from src/macos/iokit.rs rename to src/transport/macos/iokit.rs diff --git a/src/macos/mod.rs b/src/transport/macos/mod.rs similarity index 100% rename from src/macos/mod.rs rename to src/transport/macos/mod.rs diff --git a/src/macos/monitor.rs b/src/transport/macos/monitor.rs similarity index 99% rename from src/macos/monitor.rs rename to src/transport/macos/monitor.rs index 189366f9..e7425e5e 100644 --- a/src/macos/monitor.rs +++ b/src/transport/macos/monitor.rs @@ -5,7 +5,7 @@ extern crate libc; extern crate log; -use crate::platform::iokit::*; +use crate::transport::platform::iokit::*; use crate::util::io_err; use core_foundation::base::*; use core_foundation::runloop::*; diff --git a/src/macos/transaction.rs b/src/transport/macos/transaction.rs similarity index 95% rename from src/macos/transaction.rs rename to src/transport/macos/transaction.rs index 697730a4..3ad4bee4 100644 --- a/src/macos/transaction.rs +++ b/src/transport/macos/transaction.rs @@ -5,8 +5,8 @@ extern crate libc; use crate::errors; -use crate::platform::iokit::{CFRunLoopEntryObserver, IOHIDDeviceRef, SendableRunLoop}; -use crate::platform::monitor::Monitor; +use crate::transport::platform::iokit::{CFRunLoopEntryObserver, IOHIDDeviceRef, SendableRunLoop}; +use crate::transport::platform::monitor::Monitor; use crate::statecallback::StateCallback; use core_foundation::runloop::*; use std::os::raw::c_void; diff --git a/src/transport/mod.rs b/src/transport/mod.rs new file mode 100644 index 00000000..2e3dab8a --- /dev/null +++ b/src/transport/mod.rs @@ -0,0 +1,38 @@ +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +pub mod hidproto; + +#[cfg(target_os = "linux")] +#[path = "linux/mod.rs"] +pub mod platform; + +#[cfg(target_os = "freebsd")] +#[path = "freebsd/mod.rs"] +pub mod platform; + +#[cfg(target_os = "netbsd")] +#[path = "netbsd/mod.rs"] +pub mod platform; + +#[cfg(target_os = "openbsd")] +#[path = "openbsd/mod.rs"] +pub mod platform; + +#[cfg(target_os = "macos")] +#[path = "macos/mod.rs"] +pub mod platform; + +#[cfg(target_os = "windows")] +#[path = "windows/mod.rs"] +pub mod platform; + +#[cfg( + not(any( + target_os = "linux", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "macos", + target_os = "windows" +)))] +#[path = "stub/mod.rs"] +pub mod platform; diff --git a/src/netbsd/device.rs b/src/transport/netbsd/device.rs similarity index 98% rename from src/netbsd/device.rs rename to src/transport/netbsd/device.rs index 92e7c22e..91cbd8b6 100644 --- a/src/netbsd/device.rs +++ b/src/transport/netbsd/device.rs @@ -11,8 +11,8 @@ use std::mem; use crate::consts::CID_BROADCAST; use crate::consts::MAX_HID_RPT_SIZE; -use crate::platform::fd::Fd; -use crate::platform::uhid; +use crate::transport::platform::fd::Fd; +use crate::transport::platform::uhid; use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; use crate::util::io_err; diff --git a/src/netbsd/fd.rs b/src/transport/netbsd/fd.rs similarity index 100% rename from src/netbsd/fd.rs rename to src/transport/netbsd/fd.rs diff --git a/src/netbsd/mod.rs b/src/transport/netbsd/mod.rs similarity index 100% rename from src/netbsd/mod.rs rename to src/transport/netbsd/mod.rs diff --git a/src/netbsd/monitor.rs b/src/transport/netbsd/monitor.rs similarity index 98% rename from src/netbsd/monitor.rs rename to src/transport/netbsd/monitor.rs index c78cff6e..5e774705 100644 --- a/src/netbsd/monitor.rs +++ b/src/transport/netbsd/monitor.rs @@ -11,7 +11,7 @@ use std::time::Duration; use runloop::RunLoop; -use crate::platform::fd::Fd; +use crate::transport::platform::fd::Fd; // XXX Should use drvctl, but it doesn't do pubsub properly yet so // DRVGETEVENT requires write access to /dev/drvctl. Instead, for now, diff --git a/src/netbsd/transaction.rs b/src/transport/netbsd/transaction.rs similarity index 94% rename from src/netbsd/transaction.rs rename to src/transport/netbsd/transaction.rs index 21ac2125..43f23b37 100644 --- a/src/netbsd/transaction.rs +++ b/src/transport/netbsd/transaction.rs @@ -3,8 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::errors; -use crate::platform::fd::Fd; -use crate::platform::monitor::Monitor; +use crate::transport::platform::fd::Fd; +use crate::transport::platform::monitor::Monitor; use crate::statecallback::StateCallback; use runloop::RunLoop; diff --git a/src/netbsd/uhid.rs b/src/transport/netbsd/uhid.rs similarity index 98% rename from src/netbsd/uhid.rs rename to src/transport/netbsd/uhid.rs index f8d71155..a8fa3d0f 100644 --- a/src/netbsd/uhid.rs +++ b/src/transport/netbsd/uhid.rs @@ -11,7 +11,7 @@ use std::os::raw::c_uchar; use crate::hidproto::has_fido_usage; use crate::hidproto::ReportDescriptor; -use crate::platform::fd::Fd; +use crate::transport::platform::fd::Fd; use crate::util::io_err; /* sys/ioccom.h */ diff --git a/src/openbsd/device.rs b/src/transport/openbsd/device.rs similarity index 98% rename from src/openbsd/device.rs rename to src/transport/openbsd/device.rs index 2238e034..e9e2815c 100644 --- a/src/openbsd/device.rs +++ b/src/transport/openbsd/device.rs @@ -10,7 +10,7 @@ use std::io::{Read, Result, Write}; use std::mem; use crate::consts::{CID_BROADCAST, MAX_HID_RPT_SIZE}; -use crate::platform::monitor::FidoDev; +use crate::transport::platform::monitor::FidoDev; use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; use crate::util::{from_unix_result, io_err}; diff --git a/src/openbsd/mod.rs b/src/transport/openbsd/mod.rs similarity index 100% rename from src/openbsd/mod.rs rename to src/transport/openbsd/mod.rs diff --git a/src/openbsd/monitor.rs b/src/transport/openbsd/monitor.rs similarity index 100% rename from src/openbsd/monitor.rs rename to src/transport/openbsd/monitor.rs diff --git a/src/openbsd/transaction.rs b/src/transport/openbsd/transaction.rs similarity index 96% rename from src/openbsd/transaction.rs rename to src/transport/openbsd/transaction.rs index 4b85db27..9c96b269 100644 --- a/src/openbsd/transaction.rs +++ b/src/transport/openbsd/transaction.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::errors; -use crate::platform::monitor::{FidoDev, Monitor}; +use crate::transport::platform::monitor::{FidoDev, Monitor}; use crate::statecallback::StateCallback; use runloop::RunLoop; diff --git a/src/stub/device.rs b/src/transport/stub/device.rs similarity index 100% rename from src/stub/device.rs rename to src/transport/stub/device.rs diff --git a/src/stub/mod.rs b/src/transport/stub/mod.rs similarity index 100% rename from src/stub/mod.rs rename to src/transport/stub/mod.rs diff --git a/src/stub/transaction.rs b/src/transport/stub/transaction.rs similarity index 100% rename from src/stub/transaction.rs rename to src/transport/stub/transaction.rs diff --git a/src/windows/device.rs b/src/transport/windows/device.rs similarity index 100% rename from src/windows/device.rs rename to src/transport/windows/device.rs diff --git a/src/windows/mod.rs b/src/transport/windows/mod.rs similarity index 100% rename from src/windows/mod.rs rename to src/transport/windows/mod.rs diff --git a/src/windows/monitor.rs b/src/transport/windows/monitor.rs similarity index 97% rename from src/windows/monitor.rs rename to src/transport/windows/monitor.rs index 4c977bde..85b23252 100644 --- a/src/windows/monitor.rs +++ b/src/transport/windows/monitor.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use crate::platform::winapi::DeviceInfoSet; +use crate::transport::platform::winapi::DeviceInfoSet; use runloop::RunLoop; use std::collections::{HashMap, HashSet}; use std::io; diff --git a/src/windows/transaction.rs b/src/transport/windows/transaction.rs similarity index 96% rename from src/windows/transaction.rs rename to src/transport/windows/transaction.rs index 74e856b6..7711f361 100644 --- a/src/windows/transaction.rs +++ b/src/transport/windows/transaction.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::errors; -use crate::platform::monitor::Monitor; +use crate::transport::platform::monitor::Monitor; use crate::statecallback::StateCallback; use runloop::RunLoop; diff --git a/src/windows/winapi.rs b/src/transport/windows/winapi.rs similarity index 96% rename from src/windows/winapi.rs rename to src/transport/windows/winapi.rs index d3388ceb..14f141d1 100644 --- a/src/windows/winapi.rs +++ b/src/transport/windows/winapi.rs @@ -15,9 +15,9 @@ use crate::util::io_err; extern crate libc; extern crate winapi; -use crate::platform::winapi::winapi::shared::{guiddef, minwindef, ntdef, windef}; -use crate::platform::winapi::winapi::shared::{hidclass, hidpi, hidusage}; -use crate::platform::winapi::winapi::um::{handleapi, setupapi}; +use crate::transport::platformwinapi::winapi::shared::{guiddef, minwindef, ntdef, windef}; +use crate::transport::platformwinapi::winapi::shared::{hidclass, hidpi, hidusage}; +use crate::transport::platformwinapi::winapi::um::{handleapi, setupapi}; #[link(name = "setupapi")] extern "system" { From f88ff0750ebe323faf618e4f4d71d0deb7c8e425 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus <> Date: Tue, 4 May 2021 11:00:15 +0200 Subject: [PATCH 2/7] Replace capability-defines with bitflags (and add ctap2 capabilities) --- src/authenticatorservice.rs | 3 ++- src/consts.rs | 21 +++++++++++++++++++-- src/u2fprotocol.rs | 4 ++-- src/u2ftypes.rs | 8 ++++---- src/virtualdevices/software_u2f.rs | 3 ++- 5 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/authenticatorservice.rs b/src/authenticatorservice.rs index fcb05dc6..bea783f7 100644 --- a/src/authenticatorservice.rs +++ b/src/authenticatorservice.rs @@ -222,6 +222,7 @@ impl AuthenticatorService { #[cfg(test)] mod tests { use super::{AuthenticatorService, AuthenticatorTransport}; + use crate::consts::Capability; use crate::consts::PARAMETER_SIZE; use crate::statecallback::StateCallback; use crate::{AuthenticatorTransports, KeyHandle, RegisterFlags, SignFlags, StatusUpdate}; @@ -257,7 +258,7 @@ mod tests { version_major: 1, version_minor: 2, version_build: 3, - cap_flags: 0, + cap_flags: Capability::empty(), } } } diff --git a/src/consts.rs b/src/consts.rs index 5f27bb93..6bca49c5 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -57,8 +57,25 @@ pub const U2F_CHECK_IS_REGISTERED: u8 = 0x07; // Check if the key handle is regi // U2FHID_INIT command defines pub const INIT_NONCE_SIZE: usize = 8; // Size of channel initialization challenge -pub const CAPFLAG_WINK: u8 = 0x01; // Device supports WINK command -pub const CAPFLAG_LOCK: u8 = 0x02; // Device supports LOCK command + +bitflags! { + pub struct Capability: u8 { + const WINK = 0x01; + const LOCK = 0x02; + const CBOR = 0x04; + const NMSG = 0x08; + } +} + +impl Capability { + pub fn has_fido1(self) -> bool { + !self.contains(Capability::NMSG) + } + + pub fn has_fido2(self) -> bool { + self.contains(Capability::CBOR) + } +} // Low-level error codes. Return as negatives. diff --git a/src/u2fprotocol.rs b/src/u2fprotocol.rs index ca9f7043..deefe67a 100644 --- a/src/u2fprotocol.rs +++ b/src/u2fprotocol.rs @@ -235,7 +235,7 @@ mod tests { use rand::{thread_rng, RngCore}; use super::{init_device, send_apdu, sendrecv, U2FDevice}; - use crate::consts::{CID_BROADCAST, SW_NO_ERROR, U2FHID_INIT, U2FHID_MSG, U2FHID_PING}; + use crate::consts::{Capability, CID_BROADCAST, SW_NO_ERROR, U2FHID_INIT, U2FHID_MSG, U2FHID_PING}; mod platform { use std::io; @@ -376,7 +376,7 @@ mod tests { assert_eq!(dev_info.version_major, 0x04); assert_eq!(dev_info.version_minor, 0x01); assert_eq!(dev_info.version_build, 0x08); - assert_eq!(dev_info.cap_flags, 0x01); + assert_eq!(dev_info.cap_flags, Capability::WINK); // 0x01 } #[test] diff --git a/src/u2ftypes.rs b/src/u2ftypes.rs index 8360e8ad..127579b7 100644 --- a/src/u2ftypes.rs +++ b/src/u2ftypes.rs @@ -169,7 +169,7 @@ pub struct U2FHIDInitResp { pub version_major: u8, pub version_minor: u8, pub version_build: u8, - pub cap_flags: u8, + pub cap_flags: Capability, } impl U2FHIDInitResp { @@ -195,7 +195,7 @@ impl U2FHIDInitResp { version_major: data[INIT_NONCE_SIZE + 5], version_minor: data[INIT_NONCE_SIZE + 6], version_build: data[INIT_NONCE_SIZE + 7], - cap_flags: data[INIT_NONCE_SIZE + 8], + cap_flags: Capability::from_bits_truncate(data[INIT_NONCE_SIZE + 8]), }; Ok(rsp) @@ -236,7 +236,7 @@ pub struct U2FDeviceInfo { pub version_major: u8, pub version_minor: u8, pub version_build: u8, - pub cap_flags: u8, + pub cap_flags: Capability, } impl fmt::Display for U2FDeviceInfo { @@ -250,7 +250,7 @@ impl fmt::Display for U2FDeviceInfo { &self.version_major, &self.version_minor, &self.version_build, - to_hex(&[self.cap_flags], ":"), + to_hex(&[self.cap_flags.bits()], ":"), ) } } diff --git a/src/virtualdevices/software_u2f.rs b/src/virtualdevices/software_u2f.rs index a88e74de..c1283477 100644 --- a/src/virtualdevices/software_u2f.rs +++ b/src/virtualdevices/software_u2f.rs @@ -1,6 +1,7 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use crate::consts::Capability; pub struct SoftwareU2FToken {} @@ -45,7 +46,7 @@ impl SoftwareU2FToken { version_major: 1, version_minor: 2, version_build: 3, - cap_flags: 0, + cap_flags: Capability::empty(), } } } From 24a02ac2c4cd75dd5cbe95bb1ff8e378e25ad8e9 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus <> Date: Tue, 4 May 2021 11:10:26 +0200 Subject: [PATCH 3/7] Make HID commands its own type --- src/consts.rs | 66 +++++++++++++++++++++++++++++++++++++++++----- src/u2fprotocol.rs | 45 ++++++++++++++++++------------- 2 files changed, 86 insertions(+), 25 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index 6bca49c5..d04418a9 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -30,13 +30,65 @@ pub const U2FHID_IF_VERSION: u32 = 2; // Current interface implementation versio pub const U2FHID_FRAME_TIMEOUT: u32 = 500; // Default frame timeout in ms pub const U2FHID_TRANS_TIMEOUT: u32 = 3000; // Default message timeout in ms -// U2FHID native commands -pub const U2FHID_PING: u8 = TYPE_INIT | 0x01; // Echo data through local processor only -pub const U2FHID_MSG: u8 = TYPE_INIT | 0x03; // Send U2F message frame -pub const U2FHID_LOCK: u8 = TYPE_INIT | 0x04; // Send lock channel command -pub const U2FHID_INIT: u8 = TYPE_INIT | 0x06; // Channel initialization -pub const U2FHID_WINK: u8 = TYPE_INIT | 0x08; // Send device identification wink -pub const U2FHID_ERROR: u8 = TYPE_INIT | 0x3f; // Error response +// CTAPHID native commands +const CTAPHID_PING: u8 = TYPE_INIT | 0x01; // Echo data through local processor only +const CTAPHID_MSG: u8 = TYPE_INIT | 0x03; // Send U2F message frame +const CTAPHID_LOCK: u8 = TYPE_INIT | 0x04; // Send lock channel command +const CTAPHID_INIT: u8 = TYPE_INIT | 0x06; // Channel initialization +const CTAPHID_WINK: u8 = TYPE_INIT | 0x08; // Send device identification wink +const CTAPHID_CBOR: u8 = TYPE_INIT | 0x10; // Encapsulated CBOR encoded message +const CTAPHID_CANCEL: u8 = TYPE_INIT | 0x11; // Cancel outstanding requests +const CTAPHID_ERROR: u8 = TYPE_INIT | 0x3f; // Error response +const CTAPHID_KEEPALIVE: u8 = TYPE_INIT | 0x3b; // Should be sent a an authenticator every 100ms and whenever a status changes + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[repr(u8)] +pub enum HIDCmd { + Ping, + Msg, + Lock, + Init, + Wink, + Cbor, + Cancel, + Keepalive, + Error, + Unknown(u8), +} + +impl Into for HIDCmd { + fn into(self) -> u8 { + match self { + HIDCmd::Ping => CTAPHID_PING, + HIDCmd::Msg => CTAPHID_MSG, + HIDCmd::Lock => CTAPHID_LOCK, + HIDCmd::Init => CTAPHID_INIT, + HIDCmd::Wink => CTAPHID_WINK, + HIDCmd::Cbor => CTAPHID_CBOR, + HIDCmd::Cancel => CTAPHID_CANCEL, + HIDCmd::Keepalive => CTAPHID_KEEPALIVE, + HIDCmd::Error => CTAPHID_ERROR, + HIDCmd::Unknown(v) => v, + } + } +} + +impl From for HIDCmd { + fn from(v: u8) -> HIDCmd { + match v { + CTAPHID_PING => HIDCmd::Ping, + CTAPHID_MSG => HIDCmd::Msg, + CTAPHID_LOCK => HIDCmd::Lock, + CTAPHID_INIT => HIDCmd::Init, + CTAPHID_WINK => HIDCmd::Wink, + CTAPHID_CBOR => HIDCmd::Cbor, + CTAPHID_CANCEL => HIDCmd::Cancel, + CTAPHID_KEEPALIVE => HIDCmd::Keepalive, + CTAPHID_ERROR => HIDCmd::Error, + v => HIDCmd::Unknown(v), + } + } +} // U2FHID_MSG commands pub const U2F_VENDOR_FIRST: u8 = TYPE_INIT | 0x40; // First vendor defined command diff --git a/src/u2fprotocol.rs b/src/u2fprotocol.rs index deefe67a..9b79180c 100644 --- a/src/u2fprotocol.rs +++ b/src/u2fprotocol.rs @@ -127,8 +127,10 @@ where T: U2FDevice + Read + Write, { assert_eq!(nonce.len(), INIT_NONCE_SIZE); - let raw = sendrecv(dev, U2FHID_INIT, nonce)?; + // Send Init to broadcast address to create a new channel + let raw = sendrecv(dev, HIDCmd::Init, nonce)?; let rsp = U2FHIDInitResp::read(&raw, nonce)?; + // Get the new Channel ID dev.set_cid(rsp.cid); let vendor = dev @@ -181,12 +183,12 @@ fn status_word_to_result(status: [u8; 2], val: T) -> io::Result { // Device Communication Functions //////////////////////////////////////////////////////////////////////// -pub fn sendrecv(dev: &mut T, cmd: u8, send: &[u8]) -> io::Result> +pub fn sendrecv(dev: &mut T, cmd: HIDCmd, send: &[u8]) -> io::Result> where T: U2FDevice + Read + Write, { // Send initialization packet. - let mut count = U2FHIDInit::write(dev, cmd, send)?; + let mut count = U2FHIDInit::write(dev, cmd.into(), send)?; // Send continuation packets. let mut sequence = 0u8; @@ -214,8 +216,8 @@ fn send_apdu(dev: &mut T, cmd: u8, p1: u8, send: &[u8]) -> io::Result<(Vec Date: Tue, 4 May 2021 11:22:59 +0200 Subject: [PATCH 4/7] Run cargo fmt and clippy --- src/lib.rs | 2 +- src/statemachine.rs | 2 +- src/transport/freebsd/transaction.rs | 2 +- src/transport/macos/transaction.rs | 2 +- src/transport/mod.rs | 3 +-- src/transport/netbsd/transaction.rs | 2 +- src/transport/openbsd/transaction.rs | 2 +- src/transport/windows/transaction.rs | 2 +- src/u2fprotocol.rs | 2 +- src/util.rs | 4 ++-- 10 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1f660fa5..af56c005 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,8 +37,8 @@ pub use crate::capi::*; pub mod errors; pub mod statecallback; -mod virtualdevices; mod transport; +mod virtualdevices; // Keep this in sync with the constants in u2fhid-capi.h. bitflags! { diff --git a/src/statemachine.rs b/src/statemachine.rs index 4ef98632..d08c6d85 100644 --- a/src/statemachine.rs +++ b/src/statemachine.rs @@ -4,8 +4,8 @@ use crate::consts::PARAMETER_SIZE; use crate::errors; -use crate::transport::platform::{device::Device, transaction::Transaction}; use crate::statecallback::StateCallback; +use crate::transport::platform::{device::Device, transaction::Transaction}; use crate::u2fprotocol::{u2f_init_device, u2f_is_keyhandle_valid, u2f_register, u2f_sign}; use crate::u2ftypes::U2FDevice; diff --git a/src/transport/freebsd/transaction.rs b/src/transport/freebsd/transaction.rs index 67935ace..5d76847f 100644 --- a/src/transport/freebsd/transaction.rs +++ b/src/transport/freebsd/transaction.rs @@ -3,8 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::errors; -use crate::transport::platformmonitor::Monitor; use crate::statecallback::StateCallback; +use crate::transport::platformmonitor::Monitor; use runloop::RunLoop; use std::ffi::OsString; diff --git a/src/transport/macos/transaction.rs b/src/transport/macos/transaction.rs index 3ad4bee4..6487e68d 100644 --- a/src/transport/macos/transaction.rs +++ b/src/transport/macos/transaction.rs @@ -5,9 +5,9 @@ extern crate libc; use crate::errors; +use crate::statecallback::StateCallback; use crate::transport::platform::iokit::{CFRunLoopEntryObserver, IOHIDDeviceRef, SendableRunLoop}; use crate::transport::platform::monitor::Monitor; -use crate::statecallback::StateCallback; use core_foundation::runloop::*; use std::os::raw::c_void; use std::sync::mpsc::{channel, Receiver, Sender}; diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 2e3dab8a..a536cb3c 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -25,8 +25,7 @@ pub mod platform; #[path = "windows/mod.rs"] pub mod platform; -#[cfg( - not(any( +#[cfg(not(any( target_os = "linux", target_os = "freebsd", target_os = "openbsd", diff --git a/src/transport/netbsd/transaction.rs b/src/transport/netbsd/transaction.rs index 43f23b37..b1583d05 100644 --- a/src/transport/netbsd/transaction.rs +++ b/src/transport/netbsd/transaction.rs @@ -3,9 +3,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::errors; +use crate::statecallback::StateCallback; use crate::transport::platform::fd::Fd; use crate::transport::platform::monitor::Monitor; -use crate::statecallback::StateCallback; use runloop::RunLoop; pub struct Transaction { diff --git a/src/transport/openbsd/transaction.rs b/src/transport/openbsd/transaction.rs index 9c96b269..50f93486 100644 --- a/src/transport/openbsd/transaction.rs +++ b/src/transport/openbsd/transaction.rs @@ -3,8 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::errors; -use crate::transport::platform::monitor::{FidoDev, Monitor}; use crate::statecallback::StateCallback; +use crate::transport::platform::monitor::{FidoDev, Monitor}; use runloop::RunLoop; pub struct Transaction { diff --git a/src/transport/windows/transaction.rs b/src/transport/windows/transaction.rs index 7711f361..6af44d78 100644 --- a/src/transport/windows/transaction.rs +++ b/src/transport/windows/transaction.rs @@ -3,8 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::errors; -use crate::transport::platform::monitor::Monitor; use crate::statecallback::StateCallback; +use crate::transport::platform::monitor::Monitor; use runloop::RunLoop; pub struct Transaction { diff --git a/src/u2fprotocol.rs b/src/u2fprotocol.rs index 9b79180c..753e3888 100644 --- a/src/u2fprotocol.rs +++ b/src/u2fprotocol.rs @@ -216,7 +216,7 @@ fn send_apdu(dev: &mut T, cmd: u8, p1: u8, send: &[u8]) -> io::Result<(Vec bool { - *self < (0 as i32) + *self < 0 } } impl Signed for usize { fn is_negative(&self) -> bool { - (*self as isize) < (0 as isize) + (*self as isize) < 0 } } From ae6ffae14591389fca947b2e930b94b59889cc73 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus <> Date: Thu, 6 May 2021 13:31:40 +0200 Subject: [PATCH 5/7] Add test for U2F_VERSION --- src/u2fprotocol.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/u2fprotocol.rs b/src/u2fprotocol.rs index 753e3888..d1026409 100644 --- a/src/u2fprotocol.rs +++ b/src/u2fprotocol.rs @@ -380,6 +380,43 @@ mod tests { assert_eq!(dev_info.cap_flags, Capability::WINK); // 0x01 } + #[test] + fn test_get_version() { + let mut device = platform::TestDevice::new(); + // channel id + let mut cid = [0u8; 4]; + thread_rng().fill_bytes(&mut cid); + + device.set_cid(cid.clone()); + + let info = U2FDeviceInfo { + vendor_name: Vec::new(), + device_name: Vec::new(), + version_interface: 0x02, + version_major: 0x04, + version_minor: 0x01, + version_build: 0x08, + cap_flags: Capability::WINK, + }; + device.set_device_info(info); + + // ctap1 U2F_VERSION request + let mut msg = cid.to_vec(); + msg.extend(&[HIDCmd::Msg.into(), 0x0, 0x9]); // cmd + bcnt + msg.extend(&[0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); + device.add_write(&msg, 0); + + // fido response + let mut msg = cid.to_vec(); + msg.extend(&[HIDCmd::Msg.into(), 0x0, 0x08]); // cmd + bcnt + msg.extend(&[0x55, 0x32, 0x46, 0x5f, 0x56, 0x32]); // 'U2F_V2' + msg.extend(&SW_NO_ERROR); + device.add_read(&msg, 0); + + let res = is_v2_device(&mut device).expect("Failed to get version"); + assert!(res); + } + #[test] fn test_sendrecv_multiple() { let mut device = platform::TestDevice::new(); From 6035da7389c893b28034350acd914aac93774c33 Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus <> Date: Thu, 6 May 2021 14:01:57 +0200 Subject: [PATCH 6/7] CTAP2: Implement first ctap2-command GetInfo as well as ctap1-fallback GetVersion, plus tests --- Cargo.toml | 5 +- src/ctap2/commands/get_info.rs | 407 ++++++++++++++++++++++++++++++ src/ctap2/commands/get_version.rs | 118 +++++++++ src/ctap2/commands/mod.rs | 379 ++++++++++++++++++++++++++++ src/ctap2/mod.rs | 4 + src/lib.rs | 2 + src/transport/errors.rs | 98 +++++++ src/transport/hid.rs | 251 ++++++++++++++++++ src/transport/mod.rs | 69 +++++ src/u2fprotocol.rs | 23 +- src/u2ftypes.rs | 6 +- 11 files changed, 1349 insertions(+), 13 deletions(-) create mode 100644 src/ctap2/commands/get_info.rs create mode 100644 src/ctap2/commands/get_version.rs create mode 100644 src/ctap2/commands/mod.rs create mode 100644 src/ctap2/mod.rs create mode 100644 src/transport/errors.rs create mode 100644 src/transport/hid.rs diff --git a/Cargo.toml b/Cargo.toml index f6527951..bd17571a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ maintenance = { status = "actively-developed" } [features] binding-recompile = ["bindgen"] -webdriver = ["base64", "bytes", "warp", "tokio", "serde", "serde_json"] +webdriver = ["base64", "bytes", "warp", "tokio", "serde_json"] [target.'cfg(target_os = "linux")'.dependencies] libudev = "^0.2" @@ -47,7 +47,8 @@ runloop = "0.1.0" bitflags = "1.0" tokio = { version = "0.2", optional = true, features = ["macros"] } warp = { version = "0.2.4", optional = true } -serde = { version = "1.0", optional = true, features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } +serde_cbor = "0.11" serde_json = { version = "1.0", optional = true } bytes = { version = "0.5", optional = true, features = ["serde"] } base64 = { version = "^0.10", optional = true } diff --git a/src/ctap2/commands/get_info.rs b/src/ctap2/commands/get_info.rs new file mode 100644 index 00000000..8ab83edf --- /dev/null +++ b/src/ctap2/commands/get_info.rs @@ -0,0 +1,407 @@ +use super::{Command, CommandError, RequestCtap2}; +use crate::transport::errors::HIDError; +use crate::u2ftypes::U2FDevice; +use serde::{ + de::{Error as SError, MapAccess, Visitor}, + Deserialize, Deserializer, Serialize, +}; +use serde_cbor::de::from_slice; +use std::fmt; + +#[derive(Serialize, PartialEq, Eq, Clone)] +pub struct AAGuid(pub [u8; 16]); + +impl AAGuid { + fn from(src: &[u8]) -> Result { + let mut payload = [0u8; 16]; + if src.len() != payload.len() { + Err(()) + } else { + payload.copy_from_slice(src); + Ok(AAGuid(payload)) + } + } + + pub fn empty() -> Self { + AAGuid([0u8; 16]) + } +} + +impl fmt::Debug for AAGuid { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "AAGuid({:x}{:x}{:x}{:x}-{:x}{:x}-{:x}{:x}-{:x}{:x}-{:x}{:x}{:x}{:x}{:x}{:x})", + self.0[0], + self.0[1], + self.0[2], + self.0[3], + self.0[4], + self.0[5], + self.0[6], + self.0[7], + self.0[8], + self.0[9], + self.0[10], + self.0[11], + self.0[12], + self.0[13], + self.0[14], + self.0[15] + ) + } +} + +impl<'de> Deserialize<'de> for AAGuid { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct AAGuidVisitor; + + impl<'de> Visitor<'de> for AAGuidVisitor { + type Value = AAGuid; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte array") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: SError, + { + if v.len() != 16 { + return Err(E::custom("expecting 16 bytes data")); + } + + let mut buf = [0u8; 16]; + + buf.copy_from_slice(v); + + Ok(AAGuid(buf)) + } + } + + deserializer.deserialize_bytes(AAGuidVisitor) + } +} + +#[derive(Debug)] +pub struct GetInfo {} + +impl Default for GetInfo { + fn default() -> GetInfo { + GetInfo {} + } +} + +impl RequestCtap2 for GetInfo { + type Output = AuthenticatorInfo; + + fn command() -> Command { + Command::GetInfo + } + + fn wire_format(&self, _dev: &mut Dev) -> Result, HIDError> + where + Dev: U2FDevice, + { + Ok(Vec::new()) + } + + fn handle_response_ctap2( + &self, + _dev: &mut Dev, + input: &[u8], + ) -> Result + where + Dev: U2FDevice, + { + if input.is_empty() { + return Err(CommandError::InputTooSmall).map_err(HIDError::Command); + } + if input.len() > 1 { + trace!("parsing authenticator info data: {:#04X?}", &input); + let authenticator_info = from_slice(&input).map_err(CommandError::Deserializing)?; + Ok(authenticator_info) + } else { + Err(CommandError::InputTooSmall).map_err(HIDError::Command) + } + } +} + +#[derive(Debug, Deserialize, Clone, Eq, PartialEq)] +pub(crate) struct AuthenticatorOptions { + /// Indicates that the device is attached to the client and therefore can’t + /// be removed and used on another client. + #[serde(rename = "plat")] + pub(crate) platform_device: bool, + /// Indicates that the device is capable of storing keys on the device + /// itself and therefore can satisfy the authenticatorGetAssertion request + /// with allowList parameter not specified or empty. + #[serde(rename = "rk")] + pub(crate) resident_key: bool, + + /// Client PIN: + /// If present and set to true, it indicates that the device is capable of + /// accepting a PIN from the client and PIN has been set. + /// If present and set to false, it indicates that the device is capable of + /// accepting a PIN from the client and PIN has not been set yet. + /// If absent, it indicates that the device is not capable of accepting a + /// PIN from the client. + /// Client PIN is one of the ways to do user verification. + #[serde(rename = "clientPin")] + pub(crate) client_pin: Option, + + /// Indicates that the device is capable of testing user presence. + #[serde(rename = "up")] + pub(crate) user_presence: bool, + + /// Indicates that the device is capable of verifying the user within + /// itself. For example, devices with UI, biometrics fall into this + /// category. + /// If present and set to true, it indicates that the device is capable of + /// user verification within itself and has been configured. + /// If present and set to false, it indicates that the device is capable of + /// user verification within itself and has not been yet configured. For + /// example, a biometric device that has not yet been configured will + /// return this parameter set to false. + /// If absent, it indicates that the device is not capable of user + /// verification within itself. + /// A device that can only do Client PIN will not return the "uv" parameter. + /// If a device is capable of verifying the user within itself as well as + /// able to do Client PIN, it will return both "uv" and the Client PIN + /// option. + #[serde(rename = "uv")] + pub(crate) user_verification: Option, +} + +impl Default for AuthenticatorOptions { + fn default() -> Self { + AuthenticatorOptions { + platform_device: false, + resident_key: false, + client_pin: None, + user_presence: true, + user_verification: None, + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct AuthenticatorInfo { + pub(crate) versions: Vec, + pub(crate) extensions: Vec, + pub(crate) aaguid: AAGuid, + pub(crate) options: AuthenticatorOptions, + pub(crate) max_msg_size: Option, + pub(crate) pin_protocols: Vec, +} + +impl<'de> Deserialize<'de> for AuthenticatorInfo { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct AuthenticatorInfoVisitor; + + impl<'de> Visitor<'de> for AuthenticatorInfoVisitor { + type Value = AuthenticatorInfo; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte array") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut versions = Vec::new(); + let mut extensions = Vec::new(); + let mut aaguid = None; + let mut options = AuthenticatorOptions::default(); + let mut max_msg_size = None; + let mut pin_protocols = Vec::new(); + + while let Some(key) = map.next_key()? { + match key { + 1 => { + if !versions.is_empty() { + return Err(serde::de::Error::duplicate_field("versions")); + } + versions = map.next_value()?; + } + 2 => { + if !extensions.is_empty() { + return Err(serde::de::Error::duplicate_field("extensions")); + } + extensions = map.next_value()?; + } + 3 => { + if aaguid.is_some() { + return Err(serde::de::Error::duplicate_field("aaguid")); + } + aaguid = Some(map.next_value()?); + } + 4 => { + options = map.next_value()?; + } + 5 => { + max_msg_size = Some(map.next_value()?); + } + 6 => { + pin_protocols = map.next_value()?; + } + k => return Err(M::Error::custom(format!("unexpected key: {:?}", k))), + } + } + + if versions.is_empty() { + return Err(M::Error::custom( + "expected at least one version, got none".to_string(), + )); + } + + if let Some(aaguid) = aaguid { + Ok(AuthenticatorInfo { + versions, + extensions, + aaguid, + options, + max_msg_size, + pin_protocols, + }) + } else { + Err(M::Error::custom("No AAGuid specified".to_string())) + } + } + } + + deserializer.deserialize_bytes(AuthenticatorInfoVisitor) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::consts::{Capability, HIDCmd, CID_BROADCAST}; + use crate::transport::{FidoDevice, Nonce}; + use crate::u2fprotocol::tests::platform::{TestDevice, IN_HID_RPT_SIZE}; + use crate::u2ftypes::U2FDevice; + use rand::{thread_rng, RngCore}; + use serde_cbor::de::from_slice; + + // Raw data take from https://github.com/Yubico/python-fido2/blob/master/test/test_ctap2.py + pub const AAGUID_RAW: [u8; 16] = [ + 0xF8, 0xA0, 0x11, 0xF3, 0x8C, 0x0A, 0x4D, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1F, 0x9E, 0xDC, + 0x7D, + ]; + + pub const AUTHENTICATOR_INFO_PAYLOAD: [u8; 89] = [ + 0xa6, 0x01, 0x82, 0x66, 0x55, 0x32, 0x46, 0x5f, 0x56, 0x32, 0x68, 0x46, 0x49, 0x44, 0x4f, + 0x5f, 0x32, 0x5f, 0x30, 0x02, 0x82, 0x63, 0x75, 0x76, 0x6d, 0x6b, 0x68, 0x6d, 0x61, 0x63, + 0x2d, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x03, 0x50, 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, + 0x4d, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x04, 0xa4, 0x62, 0x72, 0x6b, + 0xf5, 0x62, 0x75, 0x70, 0xf5, 0x64, 0x70, 0x6c, 0x61, 0x74, 0xf4, 0x69, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x50, 0x69, 0x6e, 0xf4, 0x05, 0x19, 0x04, 0xb0, 0x06, 0x81, 0x01, + ]; + + #[test] + fn parse_authenticator_info() { + let authenticator_info: AuthenticatorInfo = + from_slice(&AUTHENTICATOR_INFO_PAYLOAD).unwrap(); + + let expected = AuthenticatorInfo { + versions: vec!["U2F_V2".to_string(), "FIDO_2_0".to_string()], + extensions: vec!["uvm".to_string(), "hmac-secret".to_string()], + aaguid: AAGuid(AAGUID_RAW), + options: AuthenticatorOptions { + platform_device: false, + resident_key: true, + client_pin: Some(false), + user_presence: true, + user_verification: None, + }, + max_msg_size: Some(1200), + pin_protocols: vec![1], + }; + + assert_eq!(authenticator_info, expected); + } + + #[test] + fn test_get_info_ctap2_only() { + let mut device = TestDevice::new(); + let nonce = [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]; + + // channel id + let mut cid = [0u8; 4]; + thread_rng().fill_bytes(&mut cid); + + // init packet + let mut msg = CID_BROADCAST.to_vec(); + msg.extend(vec![HIDCmd::Init.into(), 0x00, 0x08]); // cmd + bcnt + msg.extend_from_slice(&nonce); + device.add_write(&msg, 0); + + // init_resp packet + let mut msg = CID_BROADCAST.to_vec(); + msg.extend(vec![ + 0x06, /*HIDCmd::Init without TYPE_INIT*/ + 0x00, 0x11, + ]); // cmd + bcnt + msg.extend_from_slice(&nonce); + msg.extend_from_slice(&cid); // new channel id + + // We are setting NMSG, to signal that the device does not support CTAP1 + msg.extend(vec![0x02, 0x04, 0x01, 0x08, 0x01 | 0x04 | 0x08]); // versions + flags (wink+cbor+nmsg) + device.add_read(&msg, 0); + + // ctap2 request + let mut msg = cid.to_vec(); + msg.extend(vec![HIDCmd::Cbor.into(), 0x00, 0x1]); // cmd + bcnt + msg.extend(vec![0x04]); // authenticatorGetInfo + device.add_write(&msg, 0); + + // ctap2 response + let mut msg = cid.to_vec(); + msg.extend(vec![HIDCmd::Cbor.into(), 0x00, 0x59]); // cmd + bcnt + msg.extend(&AUTHENTICATOR_INFO_PAYLOAD[0..(IN_HID_RPT_SIZE - 7)]); + device.add_read(&msg, 0); + // Continuation package + let mut msg = cid.to_vec(); + msg.extend(vec![0x00]); // SEQ + msg.extend(&AUTHENTICATOR_INFO_PAYLOAD[(IN_HID_RPT_SIZE - 7)..]); + device.add_read(&msg, 0); + device + .init(Nonce::Use(nonce)) + .expect("Failed to init device"); + + assert_eq!(device.get_cid(), &cid); + + let dev_info = device.get_device_info(); + assert_eq!( + dev_info.cap_flags, + Capability::WINK | Capability::CBOR | Capability::NMSG + ); + + let result = + FidoDevice::get_authenticator_info(&device).expect("Didn't get any authenticator_info"); + let expected = AuthenticatorInfo { + versions: vec!["U2F_V2".to_string(), "FIDO_2_0".to_string()], + extensions: vec!["uvm".to_string(), "hmac-secret".to_string()], + aaguid: AAGuid(AAGUID_RAW), + options: AuthenticatorOptions { + platform_device: false, + resident_key: true, + client_pin: Some(false), + user_presence: true, + user_verification: None, + }, + max_msg_size: Some(1200), + pin_protocols: vec![1], + }; + + assert_eq!(result, &expected); + } +} diff --git a/src/ctap2/commands/get_version.rs b/src/ctap2/commands/get_version.rs new file mode 100644 index 00000000..7935f943 --- /dev/null +++ b/src/ctap2/commands/get_version.rs @@ -0,0 +1,118 @@ +use super::{CommandError, RequestCtap1, Retryable}; +use crate::consts::U2F_VERSION; +use crate::transport::errors::{ApduErrorStatus, HIDError}; +use crate::u2ftypes::U2FAPDUHeader; +use crate::u2ftypes::U2FDevice; + +#[allow(non_camel_case_types)] +pub enum U2FInfo { + U2F_V2, +} + +#[derive(Debug)] +// TODO(baloo): if one does not issue U2F_VERSION before makecredentials or getassertion, token +// will return error (ConditionsNotSatified), test this in unit tests +pub struct GetVersion {} + +impl Default for GetVersion { + fn default() -> GetVersion { + GetVersion {} + } +} + +impl RequestCtap1 for GetVersion { + type Output = U2FInfo; + + fn handle_response_ctap1( + &self, + _status: Result<(), ApduErrorStatus>, + input: &[u8], + ) -> Result> { + if input.is_empty() { + return Err(Retryable::Error(HIDError::Command( + CommandError::InputTooSmall, + ))); + } + + let expected = String::from("U2F_V2"); + let result = String::from_utf8_lossy(input); + match result { + ref data if data == &expected => Ok(U2FInfo::U2F_V2), + _ => Err(Retryable::Error(HIDError::UnexpectedVersion)), + } + } + + fn apdu_format(&self, _dev: &mut Dev) -> Result, HIDError> + where + Dev: U2FDevice, + { + let flags = 0; + + let cmd = U2F_VERSION; + let data = U2FAPDUHeader::serialize(cmd, flags, &[])?; + Ok(data) + } +} + +#[cfg(test)] +pub mod tests { + use crate::consts::{Capability, HIDCmd, CID_BROADCAST, SW_NO_ERROR}; + use crate::transport::{FidoDevice, Nonce}; + use crate::u2fprotocol::tests::platform::TestDevice; + use crate::u2ftypes::U2FDevice; + use rand::{thread_rng, RngCore}; + + #[test] + fn test_get_version_ctap1_only() { + let mut device = TestDevice::new(); + let nonce = [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]; + + // channel id + let mut cid = [0u8; 4]; + thread_rng().fill_bytes(&mut cid); + + // init packet + let mut msg = CID_BROADCAST.to_vec(); + msg.extend(&[HIDCmd::Init.into(), 0x00, 0x08]); // cmd + bcnt + msg.extend_from_slice(&nonce); + device.add_write(&msg, 0); + + // init_resp packet + let mut msg = CID_BROADCAST.to_vec(); + msg.extend(vec![ + 0x06, /*HIDCmd::Init without !TYPE_INIT*/ + 0x00, 0x11, + ]); // cmd + bcnt + msg.extend_from_slice(&nonce); + msg.extend_from_slice(&cid); // new channel id + + // We are not setting CBOR, to signal that the device does not support CTAP1 + msg.extend(&[0x02, 0x04, 0x01, 0x08, 0x01]); // versions + flags (wink) + device.add_read(&msg, 0); + + // ctap1 U2F_VERSION request + let mut msg = cid.to_vec(); + msg.extend(&[HIDCmd::Msg.into(), 0x0, 0x9]); // cmd + bcnt + msg.extend(&[0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]); + device.add_write(&msg, 0); + + // fido response + let mut msg = cid.to_vec(); + msg.extend(&[HIDCmd::Msg.into(), 0x0, 0x08]); // cmd + bcnt + msg.extend(&[0x55, 0x32, 0x46, 0x5f, 0x56, 0x32]); // 'U2F_V2' + msg.extend(&SW_NO_ERROR); + device.add_read(&msg, 0); + + device + .init(Nonce::Use(nonce)) + .expect("Failed to init device"); + + assert_eq!(device.get_cid(), &cid); + + let dev_info = device.get_device_info(); + assert_eq!(dev_info.cap_flags, Capability::WINK); + + let result = FidoDevice::get_authenticator_info(&device); + assert!(result.is_none()); + } +} diff --git a/src/ctap2/commands/mod.rs b/src/ctap2/commands/mod.rs new file mode 100644 index 00000000..3a4711f9 --- /dev/null +++ b/src/ctap2/commands/mod.rs @@ -0,0 +1,379 @@ +use crate::transport::errors::{ApduErrorStatus, HIDError}; +use crate::transport::FidoDevice; +use serde_cbor::error::Error as SerdeError; +use std::error::Error as StdErrorT; +use std::fmt; +use std::io::{Read, Write}; + +#[allow(dead_code)] // TODO(MS): Remove me asap +pub(crate) mod get_info; +pub(crate) mod get_version; + +pub(crate) trait Request +where + Self: fmt::Debug, + Self: RequestCtap1, + Self: RequestCtap2, +{ +} + +/// Retryable wraps an error type and may ask manager to retry sending a +/// command, this is useful for ctap1 where token will reply with "condition not +/// sufficient" because user needs to press the button. +pub(crate) enum Retryable { + Retry, + Error(T), +} + +impl Retryable { + pub fn is_retry(&self) -> bool { + match *self { + Retryable::Retry => true, + _ => false, + } + } + + pub fn is_error(&self) -> bool { + !self.is_retry() + } +} + +impl From for Retryable { + fn from(e: T) -> Self { + Retryable::Error(e) + } +} + +pub(crate) trait RequestCtap1: fmt::Debug { + type Output; + + fn apdu_format(&self, dev: &mut Dev) -> Result, HIDError> + where + Dev: FidoDevice + Read + Write + fmt::Debug; + + fn handle_response_ctap1( + &self, + status: Result<(), ApduErrorStatus>, + input: &[u8], + ) -> Result>; +} + +pub(crate) trait RequestCtap2: fmt::Debug { + type Output; + + fn command() -> Command; + + fn wire_format(&self, dev: &mut Dev) -> Result, HIDError> + where + Dev: FidoDevice + Read + Write + fmt::Debug; + + fn handle_response_ctap2( + &self, + dev: &mut Dev, + input: &[u8], + ) -> Result + where + Dev: FidoDevice + Read + Write + fmt::Debug; +} + +// Spec: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticator-api +#[repr(u8)] +#[derive(Debug)] +pub enum Command { + MakeCredentials = 0x01, + GetAssertion = 0x02, + GetInfo = 0x04, + ClientPin = 0x06, + Reset = 0x07, + GetNextAssertion = 0x08, +} + +impl Command { + #[cfg(test)] + pub fn from_u8(v: u8) -> Option { + match v { + 0x01 => Some(Command::MakeCredentials), + 0x02 => Some(Command::GetAssertion), + 0x04 => Some(Command::GetInfo), + 0x06 => Some(Command::ClientPin), + 0x07 => Some(Command::Reset), + 0x08 => Some(Command::GetNextAssertion), + _ => None, + } + } +} + +#[derive(Debug)] +pub enum StatusCode { + /// Indicates successful response. + OK, + /// The command is not a valid CTAP command. + InvalidCommand, + /// The command included an invalid parameter. + InvalidParameter, + /// Invalid message or item length. + InvalidLength, + /// Invalid message sequencing. + InvalidSeq, + /// Message timed out. + Timeout, + /// Channel busy. + ChannelBusy, + /// Command requires channel lock. + LockRequired, + /// Command not allowed on this cid. + InvalidChannel, + /// Invalid/unexpected CBOR error. + CBORUnexpectedType, + /// Error when parsing CBOR. + InvalidCBOR, + /// Missing non-optional parameter. + MissingParameter, + /// Limit for number of items exceeded. + LimitExceeded, + /// Unsupported extension. + UnsupportedExtension, + /// Valid credential found in the exclude list. + CredentialExcluded, + /// Processing (Lengthy operation is in progress). + Processing, + /// Credential not valid for the authenticator. + InvalidCredential, + /// Authentication is waiting for user interaction. + UserActionPending, + /// Processing, lengthy operation is in progress. + OperationPending, + /// No request is pending. + NoOperations, + /// Authenticator does not support requested algorithm. + UnsupportedAlgorithm, + /// Not authorized for requested operation. + OperationDenied, + /// Internal key storage is full. + KeyStoreFull, + /// No outstanding operations. + NoOperationPending, + /// Unsupported option. + UnsupportedOption, + /// Not a valid option for current operation. + InvalidOption, + /// Pending keep alive was cancelled. + KeepaliveCancel, + /// No valid credentials provided. + NoCredentials, + /// Timeout waiting for user interaction. + UserActionTimeout, + /// Continuation command, such as, authenticatorGetNextAssertion not + /// allowed. + NotAllowed, + /// PIN Invalid. + PinInvalid, + /// PIN Blocked. + PinBlocked, + /// PIN authentication,pinAuth, verification failed. + PinAuthInvalid, + /// PIN authentication,pinAuth, blocked. Requires power recycle to reset. + PinAuthBlocked, + /// No PIN has been set. + PinNotSet, + /// PIN is required for the selected operation. + PinRequired, + /// PIN policy violation. Currently only enforces minimum length. + PinPolicyViolation, + /// pinToken expired on authenticator. + PinTokenExpired, + /// Authenticator cannot handle this request due to memory constraints. + RequestTooLarge, + /// The current operation has timed out. + ActionTimeout, + /// User presence is required for the requested operation. + UpRequired, + + /// Unknown status. + Unknown(u8), +} + +impl StatusCode { + fn is_ok(&self) -> bool { + match *self { + StatusCode::OK => true, + _ => false, + } + } + + fn device_busy(&self) -> bool { + match *self { + StatusCode::ChannelBusy => true, + _ => false, + } + } +} + +impl From for StatusCode { + fn from(value: u8) -> StatusCode { + match value { + 0x00 => StatusCode::OK, + 0x01 => StatusCode::InvalidCommand, + 0x02 => StatusCode::InvalidParameter, + 0x03 => StatusCode::InvalidLength, + 0x04 => StatusCode::InvalidSeq, + 0x05 => StatusCode::Timeout, + 0x06 => StatusCode::ChannelBusy, + 0x0A => StatusCode::LockRequired, + 0x0B => StatusCode::InvalidChannel, + 0x11 => StatusCode::CBORUnexpectedType, + 0x12 => StatusCode::InvalidCBOR, + 0x14 => StatusCode::MissingParameter, + 0x15 => StatusCode::LimitExceeded, + 0x16 => StatusCode::UnsupportedExtension, + 0x19 => StatusCode::CredentialExcluded, + 0x21 => StatusCode::Processing, + 0x22 => StatusCode::InvalidCredential, + 0x23 => StatusCode::UserActionPending, + 0x24 => StatusCode::OperationPending, + 0x25 => StatusCode::NoOperations, + 0x26 => StatusCode::UnsupportedAlgorithm, + 0x27 => StatusCode::OperationDenied, + 0x28 => StatusCode::KeyStoreFull, + 0x2A => StatusCode::NoOperationPending, + 0x2B => StatusCode::UnsupportedOption, + 0x2C => StatusCode::InvalidOption, + 0x2D => StatusCode::KeepaliveCancel, + 0x2E => StatusCode::NoCredentials, + 0x2f => StatusCode::UserActionTimeout, + 0x30 => StatusCode::NotAllowed, + 0x31 => StatusCode::PinInvalid, + 0x32 => StatusCode::PinBlocked, + 0x33 => StatusCode::PinAuthInvalid, + 0x34 => StatusCode::PinAuthBlocked, + 0x35 => StatusCode::PinNotSet, + 0x36 => StatusCode::PinRequired, + 0x37 => StatusCode::PinPolicyViolation, + 0x38 => StatusCode::PinTokenExpired, + 0x39 => StatusCode::RequestTooLarge, + 0x3A => StatusCode::ActionTimeout, + 0x3B => StatusCode::UpRequired, + + othr => StatusCode::Unknown(othr), + } + } +} + +#[cfg(test)] +impl Into for StatusCode { + fn into(self) -> u8 { + match self { + StatusCode::OK => 0x00, + StatusCode::InvalidCommand => 0x01, + StatusCode::InvalidParameter => 0x02, + StatusCode::InvalidLength => 0x03, + StatusCode::InvalidSeq => 0x04, + StatusCode::Timeout => 0x05, + StatusCode::ChannelBusy => 0x06, + StatusCode::LockRequired => 0x0A, + StatusCode::InvalidChannel => 0x0B, + StatusCode::CBORUnexpectedType => 0x11, + StatusCode::InvalidCBOR => 0x12, + StatusCode::MissingParameter => 0x14, + StatusCode::LimitExceeded => 0x15, + StatusCode::UnsupportedExtension => 0x16, + StatusCode::CredentialExcluded => 0x19, + StatusCode::Processing => 0x21, + StatusCode::InvalidCredential => 0x22, + StatusCode::UserActionPending => 0x23, + StatusCode::OperationPending => 0x24, + StatusCode::NoOperations => 0x25, + StatusCode::UnsupportedAlgorithm => 0x26, + StatusCode::OperationDenied => 0x27, + StatusCode::KeyStoreFull => 0x28, + StatusCode::NoOperationPending => 0x2A, + StatusCode::UnsupportedOption => 0x2B, + StatusCode::InvalidOption => 0x2C, + StatusCode::KeepaliveCancel => 0x2D, + StatusCode::NoCredentials => 0x2E, + StatusCode::UserActionTimeout => 0x2f, + StatusCode::NotAllowed => 0x30, + StatusCode::PinInvalid => 0x31, + StatusCode::PinBlocked => 0x32, + StatusCode::PinAuthInvalid => 0x33, + StatusCode::PinAuthBlocked => 0x34, + StatusCode::PinNotSet => 0x35, + StatusCode::PinRequired => 0x36, + StatusCode::PinPolicyViolation => 0x37, + StatusCode::PinTokenExpired => 0x38, + StatusCode::RequestTooLarge => 0x39, + StatusCode::ActionTimeout => 0x3A, + StatusCode::UpRequired => 0x3B, + + StatusCode::Unknown(othr) => othr, + } + } +} + +#[derive(Debug)] +pub enum CommandError { + InputTooSmall, + MissingRequiredField(&'static str), + Deserializing(SerdeError), +} + +impl fmt::Display for CommandError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + CommandError::InputTooSmall => write!(f, "CommandError: Input is too small"), + CommandError::MissingRequiredField(field) => { + write!(f, "CommandError: Missing required field {}", field) + } + CommandError::Deserializing(ref e) => { + write!(f, "CommandError: Error while parsing: {}", e) + } + } + } +} + +impl StdErrorT for CommandError {} + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::consts::CID_BROADCAST; + use crate::ctap2::commands::get_info::AuthenticatorInfo; + use crate::transport::hid::HIDDevice; + use crate::u2fprotocol::tests::platform::TestDevice; + use crate::u2ftypes::U2FDevice; + + impl HIDDevice for TestDevice { + type Id = String; + type BuildParameters = (); // None used + + fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> { + self.authenticator_info.as_ref() + } + + fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) { + self.authenticator_info = Some(authenticator_info); + } + + fn new(_: Self::BuildParameters) -> Result + where + Self::BuildParameters: Sized, + Self: Sized, + { + Ok(TestDevice { + cid: CID_BROADCAST, + reads: vec![], + writes: vec![], + dev_info: None, + authenticator_info: None, + }) + } + + fn initialized(&self) -> bool { + self.get_cid() != &CID_BROADCAST + } + + fn id(&self) -> Self::Id { + "TestDevice".to_string() + } + } +} diff --git a/src/ctap2/mod.rs b/src/ctap2/mod.rs new file mode 100644 index 00000000..398fb7db --- /dev/null +++ b/src/ctap2/mod.rs @@ -0,0 +1,4 @@ +#[allow(dead_code)] // TODO(MS): Remove me asap +pub mod commands; + +// TODO: More here soon diff --git a/src/lib.rs b/src/lib.rs index af56c005..30e88ae4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,8 @@ pub use crate::manager::U2FManager; mod capi; pub use crate::capi::*; +pub mod ctap2; + pub mod errors; pub mod statecallback; mod transport; diff --git a/src/transport/errors.rs b/src/transport/errors.rs new file mode 100644 index 00000000..463b7cb5 --- /dev/null +++ b/src/transport/errors.rs @@ -0,0 +1,98 @@ +use crate::consts::{SW_CONDITIONS_NOT_SATISFIED, SW_NO_ERROR, SW_WRONG_DATA, SW_WRONG_LENGTH}; +use crate::ctap2::commands::CommandError; +use std::fmt; +use std::io; +use std::path; + +#[allow(unused)] +#[derive(Debug, PartialEq, Eq)] +pub enum ApduErrorStatus { + ConditionsNotSatisfied, + WrongData, + WrongLength, + Unknown([u8; 2]), +} + +impl ApduErrorStatus { + pub fn from(status: [u8; 2]) -> Result<(), ApduErrorStatus> { + match status { + s if s == SW_NO_ERROR => Ok(()), + s if s == SW_CONDITIONS_NOT_SATISFIED => Err(ApduErrorStatus::ConditionsNotSatisfied), + s if s == SW_WRONG_DATA => Err(ApduErrorStatus::WrongData), + s if s == SW_WRONG_LENGTH => Err(ApduErrorStatus::WrongLength), + other => Err(ApduErrorStatus::Unknown(other)), + } + } +} + +impl fmt::Display for ApduErrorStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ApduErrorStatus::ConditionsNotSatisfied => write!(f, "Apdu: condition not satisfied"), + ApduErrorStatus::WrongData => write!(f, "Apdu: wrong data"), + ApduErrorStatus::WrongLength => write!(f, "Apdu: wrong length"), + ApduErrorStatus::Unknown(ref u) => write!(f, "Apdu: unknown error: {:?}", u), + } + } +} + +#[allow(unused)] +#[derive(Debug)] +pub enum HIDError { + /// Transport replied with a status not expected + DeviceError, + UnexpectedInitReplyLen, + NonceMismatch, + DeviceNotInitialized, + DeviceNotSupported, + UnsupportedCommand, + UnexpectedVersion, + IO(Option, io::Error), + UnexpectedCmd(u8), + Command(CommandError), + ApduStatus(ApduErrorStatus), +} + +impl From for HIDError { + fn from(e: io::Error) -> HIDError { + HIDError::IO(None, e) + } +} + +impl From for HIDError { + fn from(e: CommandError) -> HIDError { + HIDError::Command(e) + } +} + +impl From for HIDError { + fn from(e: ApduErrorStatus) -> HIDError { + HIDError::ApduStatus(e) + } +} + +impl fmt::Display for HIDError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + HIDError::UnexpectedInitReplyLen => { + write!(f, "Error: Unexpected reply len when initilizaling") + } + HIDError::NonceMismatch => write!(f, "Error: Nonce mismatch"), + HIDError::DeviceError => write!(f, "Error: device returned error"), + HIDError::DeviceNotInitialized => write!(f, "Error: using not initiliazed device"), + HIDError::DeviceNotSupported => { + write!(f, "Error: requested operation is not available on device") + } + HIDError::UnexpectedVersion => write!(f, "Error: Unexpected protocol version"), + HIDError::UnsupportedCommand => { + write!(f, "Error: command is not supported on this device") + } + HIDError::IO(ref p, ref e) => write!(f, "Error: Ioerror({:?}): {}", p, e), + HIDError::Command(ref e) => write!(f, "Error: Error issuing command: {}", e), + HIDError::UnexpectedCmd(s) => write!(f, "Error: Unexpected status: {}", s), + HIDError::ApduStatus(ref status) => { + write!(f, "Error: Unexpected apdu status: {:?}", status) + } + } + } +} diff --git a/src/transport/hid.rs b/src/transport/hid.rs new file mode 100644 index 00000000..8c6cbc50 --- /dev/null +++ b/src/transport/hid.rs @@ -0,0 +1,251 @@ +use crate::consts::{HIDCmd, CID_BROADCAST}; +use crate::ctap2::commands::get_info::{AuthenticatorInfo, GetInfo}; +use crate::ctap2::commands::get_version::GetVersion; +use crate::ctap2::commands::{RequestCtap1, RequestCtap2, Retryable}; +use crate::transport::{ + errors::{ApduErrorStatus, HIDError}, + FidoDevice, Nonce, +}; +use crate::u2ftypes::{U2FDevice, U2FDeviceInfo, U2FHIDCont, U2FHIDInit, U2FHIDInitResp}; +use crate::util::io_err; +use rand::{thread_rng, RngCore}; +use std::fmt; +use std::io; +use std::thread; +use std::time::Duration; + +pub trait HIDDevice +where + Self: io::Read, + Self: io::Write, + Self: U2FDevice, + Self: Sized, +{ + type BuildParameters; + type Id: fmt::Debug; + + fn new(parameters: Self::BuildParameters) -> Result + where + Self::BuildParameters: Sized, + Self: Sized; + + fn initialized(&self) -> bool; + + fn id(&self) -> Self::Id; + + fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo>; + fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo); + + fn initialize(&mut self, noncecmd: Nonce) -> Result<(), HIDError> + where + Self::Id: fmt::Debug, + { + if self.initialized() { + return Ok(()); + } + + let nonce = match noncecmd { + Nonce::Use(x) => x, + Nonce::CreateRandom => { + let mut nonce = [0u8; 8]; + thread_rng().fill_bytes(&mut nonce); + nonce + } + }; + + // Send Init to broadcast address to create a new channel + self.set_cid(CID_BROADCAST); + let (cmd, raw) = self.sendrecv(HIDCmd::Init, &nonce)?; + if cmd != HIDCmd::Init { + return Err(HIDError::DeviceError); + } + + let rsp = U2FHIDInitResp::read(&raw, &nonce)?; + + // Get the new Channel ID + self.set_cid(rsp.cid); + + let vendor = self + .get_property("Manufacturer") + .unwrap_or_else(|_| String::from("Unknown Vendor")); + let product = self + .get_property("Product") + .unwrap_or_else(|_| String::from("Unknown Device")); + + self.set_device_info(U2FDeviceInfo { + vendor_name: vendor.as_bytes().to_vec(), + device_name: product.as_bytes().to_vec(), + version_interface: rsp.version_interface, + version_major: rsp.version_major, + version_minor: rsp.version_minor, + version_build: rsp.version_build, + cap_flags: rsp.cap_flags, + }); + + // A CTAPHID host SHALL accept a response size that is longer than the + // anticipated size to allow for future extensions of the protocol, yet + // maintaining backwards compatibility. Future versions will maintain + // the response structure of the current version, but additional fields + // may be added. + + Ok(()) + } + + fn sendrecv(&mut self, cmd: HIDCmd, send: &[u8]) -> io::Result<(HIDCmd, Vec)> + where + Self::Id: fmt::Debug, + { + let cmd: u8 = cmd.into(); + self.u2f_write(cmd, send)?; + loop { + let (cmd, data) = self.u2f_read()?; + if cmd != HIDCmd::Keepalive { + break Ok((cmd, data)); + } + } + } + + fn u2f_write(&mut self, cmd: u8, send: &[u8]) -> io::Result<()> + where + Self::Id: fmt::Debug, + { + let mut count = U2FHIDInit::write(self, cmd, send)?; + + // Send continuation packets. + let mut sequence = 0u8; + while count < send.len() { + count += U2FHIDCont::write(self, sequence, &send[count..])?; + sequence += 1; + } + + Ok(()) + } + + fn u2f_read(&mut self) -> io::Result<(HIDCmd, Vec)> + where + Self::Id: fmt::Debug, + { + // Now we read. This happens in 2 chunks: The initial packet, which has + // the size we expect overall, then continuation packets, which will + // fill in data until we have everything. + let (cmd, data) = { + let (cmd, mut data) = U2FHIDInit::read(self)?; + + trace!("init frame data read: {:#04X?}", &data); + let mut sequence = 0u8; + while data.len() < data.capacity() { + let max = data.capacity() - data.len(); + data.extend_from_slice(&U2FHIDCont::read(self, sequence, max)?); + sequence += 1; + } + (cmd, data) + }; + + trace!("u2f_read({:?}) cmd={:?}: {:#04X?}", self.id(), cmd, &&data); + Ok((cmd, data)) + } +} + +impl FidoDevice for T +where + T: HIDDevice + U2FDevice + fmt::Debug, + ::Id: fmt::Debug, +{ + type BuildParameters = ::BuildParameters; + + fn send_cbor<'msg, Req: RequestCtap2>( + &mut self, + msg: &'msg Req, + ) -> Result { + debug!("sending {:?} to {:?}", msg, self); + + let mut data = msg.wire_format(self)?; + let mut buf: Vec = Vec::with_capacity(data.len() + 1); + // CTAP2 command + buf.push(Req::command() as u8); + // payload + buf.append(&mut data); + let buf = buf; + + let (cmd, resp) = self.sendrecv(HIDCmd::Cbor, &buf)?; + debug!("got from {:?} status={:?}: {:?}", self, cmd, resp); + if cmd == HIDCmd::Cbor { + Ok(msg.handle_response_ctap2(self, &resp)?) + } else { + Err(HIDError::UnexpectedCmd(cmd.into())) + } + } + + fn send_apdu<'msg, Req: RequestCtap1>( + &mut self, + msg: &'msg Req, + ) -> Result { + debug!("sending {:?} to {:?}", msg, self); + let data = msg.apdu_format(self)?; + + loop { + let (cmd, mut data) = self.sendrecv(HIDCmd::Msg, &data)?; + debug!("got from {:?} status={:?}: {:?}", self, cmd, data); + if cmd == HIDCmd::Msg { + if data.len() < 2 { + return Err(io_err("Unexpected Response: shorter than expected").into()); + } + let split_at = data.len() - 2; + let status = data.split_off(split_at); + // This will bubble up error if status != no error + let status = ApduErrorStatus::from([status[0], status[1]]); + + match msg.handle_response_ctap1(status, &data) { + Ok(out) => return Ok(out), + Err(Retryable::Retry) => { + // sleep 100ms then loop again + // TODO(baloo): meh, use tokio instead? + thread::sleep(Duration::from_millis(100)); + } + Err(Retryable::Error(e)) => return Err(e), + } + } else { + return Err(HIDError::UnexpectedCmd(cmd.into())); + } + } + } + + fn new(parameters: Self::BuildParameters) -> Result + where + Self::BuildParameters: Sized, + Self: Sized, + { + ::new(parameters) + } + + fn init(&mut self, nonce: Nonce) -> Result<(), HIDError> { + let resp = ::initialize(self, nonce); + // TODO(baloo): this logic should be moved to + // transport/mod.rs::Device trait + if self.supports_ctap2() { + let command = GetInfo::default(); + let info = self.send_cbor(&command)?; + debug!("{:?} infos: {:?}", self.id(), info); + + self.set_authenticator_info(info); + } + if self.supports_ctap1() { + let command = GetVersion::default(); + // We don't really use the result here + self.send_apdu(&command)?; + } + resp + } + + fn initialized(&self) -> bool { + ::initialized(self) + } + + fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) { + ::set_authenticator_info(self, authenticator_info) + } + + fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> { + ::get_authenticator_info(self) + } +} diff --git a/src/transport/mod.rs b/src/transport/mod.rs index a536cb3c..6bcb340d 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -1,3 +1,14 @@ +use crate::consts::Capability; +use crate::ctap2::commands::get_info::AuthenticatorInfo; +use crate::ctap2::commands::{Request, RequestCtap1, RequestCtap2}; +use crate::u2ftypes::U2FDevice; +use std::fmt; + +pub mod errors; +pub mod hid; + +use errors::HIDError; + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] pub mod hidproto; @@ -35,3 +46,61 @@ pub mod platform; )))] #[path = "stub/mod.rs"] pub mod platform; + +#[allow(dead_code)] +#[derive(Debug)] +pub enum Nonce { + CreateRandom, + Use([u8; 8]), +} + +// TODO(MS): This is the lazy way: FidoDevice currently only extends U2FDevice by more functions, +// but the goal is to remove U2FDevice entirely and copy over the trait-definition here +pub(crate) trait FidoDevice: U2FDevice +where + Self: fmt::Debug, +{ + type BuildParameters; + + fn send_msg<'msg, Out, Req: Request>(&mut self, msg: &'msg Req) -> Result { + if !self.initialized() { + return Err(HIDError::DeviceNotInitialized); + } + + if self.supports_ctap2() { + self.send_cbor(msg) + } else { + self.send_apdu(msg) + } + } + + fn send_apdu<'msg, Req: RequestCtap1>( + &mut self, + msg: &'msg Req, + ) -> Result; + fn send_cbor<'msg, Req: RequestCtap2>( + &mut self, + msg: &'msg Req, + ) -> Result; + + fn new(parameters: Self::BuildParameters) -> Result + where + Self::BuildParameters: Sized, + Self: Sized; + + fn init(&mut self, nonce: Nonce) -> Result<(), HIDError>; + + fn initialized(&self) -> bool; + fn supports_ctap1(&self) -> bool { + // CAPABILITY_NMSG: + // If set to 1, authenticator DOES NOT implement U2FHID_MSG function + !self.get_device_info().cap_flags.contains(Capability::NMSG) + } + + fn supports_ctap2(&self) -> bool { + self.get_device_info().cap_flags.contains(Capability::CBOR) + } + + fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo>; + fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo); +} diff --git a/src/u2fprotocol.rs b/src/u2fprotocol.rs index d1026409..d41d31ec 100644 --- a/src/u2fprotocol.rs +++ b/src/u2fprotocol.rs @@ -200,7 +200,7 @@ where // Now we read. This happens in 2 chunks: The initial packet, which has the // size we expect overall, then continuation packets, which will fill in // data until we have everything. - let mut data = U2FHIDInit::read(dev)?; + let (_, mut data) = U2FHIDInit::read(dev)?; let mut sequence = 0u8; while data.len() < data.capacity() { @@ -233,26 +233,30 @@ where //////////////////////////////////////////////////////////////////////// #[cfg(test)] -mod tests { - use super::{init_device, send_apdu, sendrecv, U2FDevice}; +pub(crate) mod tests { + use super::{init_device, is_v2_device, send_apdu, sendrecv, U2FDevice}; use crate::consts::{Capability, HIDCmd, CID_BROADCAST, SW_NO_ERROR}; + use crate::u2ftypes::U2FDeviceInfo; use rand::{thread_rng, RngCore}; - mod platform { + pub(crate) mod platform { use std::io; use std::io::{Read, Write}; use crate::consts::CID_BROADCAST; + use crate::ctap2::commands::get_info::AuthenticatorInfo; use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; - const IN_HID_RPT_SIZE: usize = 64; + pub(crate) const IN_HID_RPT_SIZE: usize = 64; const OUT_HID_RPT_SIZE: usize = 64; + #[derive(Debug)] pub struct TestDevice { - cid: [u8; 4], - reads: Vec<[u8; IN_HID_RPT_SIZE]>, - writes: Vec<[u8; OUT_HID_RPT_SIZE + 1]>, - dev_info: Option, + pub cid: [u8; 4], + pub reads: Vec<[u8; IN_HID_RPT_SIZE]>, + pub writes: Vec<[u8; OUT_HID_RPT_SIZE + 1]>, + pub dev_info: Option, + pub authenticator_info: Option, } impl TestDevice { @@ -262,6 +266,7 @@ mod tests { reads: vec![], writes: vec![], dev_info: None, + authenticator_info: None, } } diff --git a/src/u2ftypes.rs b/src/u2ftypes.rs index 127579b7..feda89cf 100644 --- a/src/u2ftypes.rs +++ b/src/u2ftypes.rs @@ -54,7 +54,7 @@ pub trait U2FDevice { pub struct U2FHIDInit {} impl U2FHIDInit { - pub fn read(dev: &mut T) -> io::Result> + pub fn read(dev: &mut T) -> io::Result<(HIDCmd, Vec)> where T: U2FDevice + io::Read, { @@ -69,13 +69,15 @@ impl U2FHIDInit { return Err(io_err("invalid init packet")); } + let cmd = HIDCmd::from(frame[4] | TYPE_INIT); + let cap = (frame[5] as usize) << 8 | (frame[6] as usize); let mut data = Vec::with_capacity(cap); let len = cmp::min(cap, dev.in_init_data_size()); data.extend_from_slice(&frame[7..7 + len]); - Ok(data) + Ok((cmd, data)) } pub fn write(dev: &mut T, cmd: u8, data: &[u8]) -> io::Result From 937d54d13c3e0ab2bff4c4e7e84cc4fc8f8eb03f Mon Sep 17 00:00:00 2001 From: Martin Sirringhaus <> Date: Fri, 28 May 2021 14:31:39 +0200 Subject: [PATCH 7/7] Sort CTAPHID const values according to value --- src/consts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/consts.rs b/src/consts.rs index d04418a9..7e3cbefb 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -38,8 +38,8 @@ const CTAPHID_INIT: u8 = TYPE_INIT | 0x06; // Channel initialization const CTAPHID_WINK: u8 = TYPE_INIT | 0x08; // Send device identification wink const CTAPHID_CBOR: u8 = TYPE_INIT | 0x10; // Encapsulated CBOR encoded message const CTAPHID_CANCEL: u8 = TYPE_INIT | 0x11; // Cancel outstanding requests +const CTAPHID_KEEPALIVE: u8 = TYPE_INIT | 0x3b; // Keepalive sent to authenticator every 100ms and whenever a status changes const CTAPHID_ERROR: u8 = TYPE_INIT | 0x3f; // Error response -const CTAPHID_KEEPALIVE: u8 = TYPE_INIT | 0x3b; // Should be sent a an authenticator every 100ms and whenever a status changes #[derive(Debug, PartialEq, Eq, Copy, Clone)] #[repr(u8)]