Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect if no devices are connected and send a StatusUpdate accordingly. #315

Open
wants to merge 1 commit into
base: ctap2-2021
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/ctap2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ fn main() {
let (status_tx, status_rx) = channel::<StatusUpdate>();
thread::spawn(move || loop {
match status_rx.recv() {
Ok(StatusUpdate::NoDevicesFound) => {
println!("STATUS: No device found. Please connect one!");
}
Ok(StatusUpdate::InteractiveManagement(..)) => {
panic!("STATUS: This can't happen when doing non-interactive usage");
}
Expand Down
21 changes: 15 additions & 6 deletions examples/ctap2_discoverable_creds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ use authenticator::{
};
use getopts::Options;
use sha2::{Digest, Sha256};
use std::io::Write;
use std::sync::mpsc::{channel, RecvError};
use std::{env, io, thread};
use std::io::Write;

fn print_usage(program: &str, opts: Options) {
println!("------------------------------------------------------------------------");
Expand Down Expand Up @@ -84,6 +84,9 @@ fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms:
Ok(StatusUpdate::InteractiveManagement(..)) => {
panic!("STATUS: This can't happen when doing non-interactive usage");
}
Ok(StatusUpdate::NoDevicesFound) => {
println!("STATUS: No device found. Please connect one!");
}
Ok(StatusUpdate::SelectDeviceNotice) => {
println!("STATUS: Please select a device by touching one of them.");
}
Expand Down Expand Up @@ -216,10 +219,7 @@ fn main() {
"timeout in seconds",
"SEC",
);
opts.optflag(
"s",
"skip_reg",
"Skip registration");
opts.optflag("s", "skip_reg", "Skip registration");

opts.optflag("h", "help", "print this help menu");
let matches = match opts.parse(&args[1..]) {
Expand Down Expand Up @@ -273,6 +273,9 @@ fn main() {
Ok(StatusUpdate::InteractiveManagement(..)) => {
panic!("STATUS: This can't happen when doing non-interactive usage");
}
Ok(StatusUpdate::NoDevicesFound) => {
println!("STATUS: No device found. Please connect one!");
}
Ok(StatusUpdate::SelectDeviceNotice) => {
println!("STATUS: Please select a device by touching one of them.");
}
Expand Down Expand Up @@ -368,7 +371,13 @@ fn main() {
println!("Found credentials:");
println!(
"{:?}",
assertion_object.assertion.user.clone().unwrap().name.unwrap() // Unwrapping here, as these shouldn't fail
assertion_object
.assertion
.user
.clone()
.unwrap()
.name
.unwrap() // Unwrapping here, as these shouldn't fail
);
println!("-----------------------------------------------------------------");
println!("Done.");
Expand Down
3 changes: 3 additions & 0 deletions examples/interactive_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,9 @@ fn interactive_status_callback(status_rx: Receiver<StatusUpdate>) {
);
continue;
}
Ok(StatusUpdate::NoDevicesFound) => {
println!("STATUS: No device found. Please connect one!");
}
Ok(StatusUpdate::SelectDeviceNotice) => {
println!("STATUS: Please select a device by touching one of them.");
}
Expand Down
3 changes: 3 additions & 0 deletions examples/reset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ fn main() {

loop {
match status_rx.recv() {
Ok(StatusUpdate::NoDevicesFound) => {
println!("STATUS: No device found. Please connect one!");
}
Ok(StatusUpdate::SelectDeviceNotice) => {
println!("ERROR: Please unplug all other tokens that should not be reset!");
// Needed to give the tokens enough time to start blinking
Expand Down
3 changes: 3 additions & 0 deletions examples/set_pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ fn main() {
Ok(StatusUpdate::InteractiveManagement(..)) => {
panic!("STATUS: This can't happen when doing non-interactive usage");
}
Ok(StatusUpdate::NoDevicesFound) => {
println!("STATUS: No device found. Please connect one!");
}
Ok(StatusUpdate::SelectDeviceNotice) => {
println!("STATUS: Please select a device by touching one of them.");
}
Expand Down
3 changes: 3 additions & 0 deletions examples/test_exclude_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ fn main() {
Ok(StatusUpdate::InteractiveManagement(..)) => {
panic!("STATUS: This can't happen when doing non-interactive usage");
}
Ok(StatusUpdate::NoDevicesFound) => {
println!("STATUS: No device found. Please connect one!");
}
Ok(StatusUpdate::SelectDeviceNotice) => {
println!("STATUS: Please select a device by touching one of them.");
}
Expand Down
4 changes: 1 addition & 3 deletions src/ctap2/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use crate::crypto::{CryptoError, PinUvAuthParam, PinUvAuthToken};
use crate::ctap2::commands::client_pin::{
GetPinRetries, GetUvRetries, PinError,
};
use crate::ctap2::commands::client_pin::{GetPinRetries, GetUvRetries, PinError};
use crate::ctap2::commands::get_info::AuthenticatorInfo;
use crate::ctap2::server::UserVerificationRequirement;
use crate::errors::AuthenticatorError;
Expand Down
2 changes: 2 additions & 0 deletions src/status_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ pub enum StatusUpdate {
InteractiveManagement(InteractiveUpdate),
/// Sent when a token returns multiple results for a getAssertion request
SelectResultNotice(Sender<Option<usize>>, Vec<PublicKeyCredentialUserEntity>),
/// Inform user that no devices are plugged in
NoDevicesFound,
}

pub(crate) fn send_status(status: &Sender<StatusUpdate>, msg: StatusUpdate) {
Expand Down
97 changes: 91 additions & 6 deletions src/transport/device_selector.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::status_update::send_status;
use crate::transport::hid::HIDDevice;
use crate::StatusUpdate;

pub use crate::transport::platform::device::Device;

Expand Down Expand Up @@ -45,7 +47,7 @@ pub struct DeviceSelector {
}

impl DeviceSelector {
pub fn run() -> Self {
pub fn run(status: Sender<crate::StatusUpdate>) -> Self {
let (selector_send, selector_rec) = channel();
// let new_device_callback = Arc::new(new_device_cb);
let runloop = RunLoop::new(move |alive| {
Expand Down Expand Up @@ -77,6 +79,9 @@ impl DeviceSelector {
break; // We are done here. The selected device continues without us.
}
DeviceSelectorEvent::DevicesAdded(ids) => {
if ids.is_empty() && waiting_for_response.is_empty() && tokens.is_empty() {
send_status(&status, StatusUpdate::NoDevicesFound);
}
for id in ids {
debug!("Device added event: {:?}", id);
waiting_for_response.insert(id);
Expand All @@ -99,9 +104,15 @@ impl DeviceSelector {
tokens.retain(|dev_id, _| dev_id != id);
if tokens.is_empty() {
blinking = false;
if waiting_for_response.is_empty() {
send_status(&status, StatusUpdate::NoDevicesFound);
}
continue;
}
}
if waiting_for_response.is_empty() && tokens.is_empty() {
send_status(&status, StatusUpdate::NoDevicesFound);
}
// We are already blinking, so no need to run the code below this match
// that figures out if we should blink or not. In fact, currently, we do
// NOT want to run this code again, because if you have 2 blinking tokens
Expand All @@ -115,6 +126,9 @@ impl DeviceSelector {
DeviceSelectorEvent::NotAToken(ref id) => {
debug!("Device not a token event: {:?}", id);
waiting_for_response.remove(id);
if waiting_for_response.is_empty() && tokens.is_empty() {
send_status(&status, StatusUpdate::NoDevicesFound);
}
}
DeviceSelectorEvent::ImAToken((id, tx)) => {
let _ = waiting_for_response.remove(&id);
Expand Down Expand Up @@ -187,6 +201,7 @@ pub mod tests {
transport::FidoDevice,
u2ftypes::U2FDeviceInfo,
};
use std::sync::mpsc::TryRecvError;

fn gen_info(id: String) -> U2FDeviceInfo {
U2FDeviceInfo {
Expand Down Expand Up @@ -267,9 +282,10 @@ pub mod tests {
Device::new("device selector 4").unwrap(),
];

let (tx, rx) = channel();
// Make those actual tokens. The rest is interpreted as non-u2f-devices
make_device_with_pin(&mut devices[2]);
let selector = DeviceSelector::run();
let selector = DeviceSelector::run(tx);

// Adding all
add_devices(devices.iter(), &selector);
Expand All @@ -278,6 +294,7 @@ pub mod tests {
send_no_token(d, &selector);
}
});
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));

send_i_am_token(&devices[2], &selector);

Expand All @@ -292,18 +309,24 @@ pub mod tests {
fn test_device_selector_stop() {
let device = Device::new("device selector 1").unwrap();

let mut selector = DeviceSelector::run();
let (tx, rx) = channel();
let mut selector = DeviceSelector::run(tx);

// Adding all
selector
.clone_sender()
.send(DeviceSelectorEvent::DevicesAdded(vec![device.id()]))
.unwrap();
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));

selector
.clone_sender()
.send(DeviceSelectorEvent::NotAToken(device.id()))
.unwrap();
assert_matches!(
rx.recv_timeout(Duration::from_millis(500)),
Ok(StatusUpdate::NoDevicesFound)
);
selector.stop();
}

Expand All @@ -323,7 +346,8 @@ pub mod tests {
make_device_with_pin(&mut devices[4]);
make_device_with_pin(&mut devices[5]);

let selector = DeviceSelector::run();
let (tx, rx) = channel();
let selector = DeviceSelector::run(tx);

// Adding all, except the last one (we simulate that this one is not yet plugged in)
add_devices(devices.iter().take(5), &selector);
Expand Down Expand Up @@ -355,6 +379,7 @@ pub mod tests {
devices[5].receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Blink
);
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));
}

#[test]
Expand All @@ -375,7 +400,8 @@ pub mod tests {
make_device_simple_u2f(&mut devices[4]);
make_device_simple_u2f(&mut devices[5]);

let selector = DeviceSelector::run();
let (tx, rx) = channel();
let selector = DeviceSelector::run(tx);

// Adding all, except the last one (we simulate that this one is not yet plugged in)
add_devices(devices.iter().take(5), &selector);
Expand Down Expand Up @@ -417,6 +443,7 @@ pub mod tests {
devices[6].receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Blink
);
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));
}

#[test]
Expand All @@ -437,7 +464,8 @@ pub mod tests {
make_device_with_pin(&mut devices[4]);
make_device_with_pin(&mut devices[5]);

let selector = DeviceSelector::run();
let (tx, rx) = channel();
let selector = DeviceSelector::run(tx);

// Adding all, except the last one (we simulate that this one is not yet plugged in)
add_devices(devices.iter(), &selector);
Expand All @@ -456,11 +484,16 @@ pub mod tests {
DeviceCommand::Blink
);
}
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));

// Remove all tokens
for idx in [2, 4, 5] {
remove_device(&devices[idx], &selector);
}
assert_matches!(
rx.recv_timeout(Duration::from_millis(500)),
Ok(StatusUpdate::NoDevicesFound)
);

// Adding one again
send_i_am_token(&devices[4], &selector);
Expand All @@ -471,4 +504,56 @@ pub mod tests {
DeviceCommand::Continue
);
}

#[test]
fn test_device_selector_no_devices() {
let mut devices = vec![
Device::new("device selector 1").unwrap(),
Device::new("device selector 2").unwrap(),
Device::new("device selector 3").unwrap(),
Device::new("device selector 4").unwrap(),
];

let (tx, rx) = channel();
// Make those actual tokens. The rest is interpreted as non-u2f-devices
make_device_with_pin(&mut devices[2]);
make_device_with_pin(&mut devices[3]);
let selector = DeviceSelector::run(tx);

// Adding no devices first (none are plugged in when we start)
add_devices(std::iter::empty(), &selector);
assert_matches!(
rx.recv_timeout(Duration::from_millis(500)),
Ok(StatusUpdate::NoDevicesFound)
);

// Adding the devices
add_devices(devices.iter(), &selector);
devices.iter_mut().for_each(|d| {
if !d.is_u2f() {
send_no_token(d, &selector);
}
});
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));

send_i_am_token(&devices[2], &selector);
send_i_am_token(&devices[3], &selector);

assert_eq!(
devices[2].receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Blink
);
assert_eq!(
devices[3].receiver.as_ref().unwrap().recv().unwrap(),
DeviceCommand::Blink
);

// Removing all blinking devices
remove_device(&devices[2], &selector);
remove_device(&devices[3], &selector);
assert_matches!(
rx.recv_timeout(Duration::from_millis(500)),
Ok(StatusUpdate::NoDevicesFound)
);
}
}
2 changes: 1 addition & 1 deletion src/transport/freebsd/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl Transaction {
+ 'static,
T: 'static,
{
let device_selector = DeviceSelector::run();
let device_selector = DeviceSelector::run(status.clone());
let selector_sender = device_selector.clone_sender();
let thread = RunLoop::new_with_timeout(
move |alive| {
Expand Down
2 changes: 1 addition & 1 deletion src/transport/linux/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl Transaction {
+ 'static,
T: 'static,
{
let device_selector = DeviceSelector::run();
let device_selector = DeviceSelector::run(status.clone());
let selector_sender = device_selector.clone_sender();
let thread = RunLoop::new_with_timeout(
move |alive| {
Expand Down
2 changes: 1 addition & 1 deletion src/transport/macos/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl Transaction {
{
let (tx, rx) = channel();
let timeout = (timeout as f64) / 1000.0;
let device_selector = DeviceSelector::run();
let device_selector = DeviceSelector::run(status.clone());
let selector_sender = device_selector.clone_sender();
let builder = thread::Builder::new();
let thread = builder
Expand Down
Loading
Loading