diff --git a/Cargo.lock b/Cargo.lock index 6e6651f..1b4c380 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,6 +60,7 @@ dependencies = [ "log", "man", "p256", + "pcsc", "rand 0.7.3", "secrecy", "sha2", diff --git a/Cargo.toml b/Cargo.toml index ab6dc84..869a7e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ gumdrop = "0.8" hex = "0.4" log = "0.4" p256 = { version = "0.7", features = ["ecdh"] } +pcsc = "2.4" rand = "0.7" secrecy = "0.7" sha2 = "0.9" diff --git a/src/main.rs b/src/main.rs index 7080d18..3705070 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ use age_plugin::run_state_machine; use dialoguer::{Confirm, Select}; use gumdrop::Options; -use log::warn; use yubikey_piv::{ certificate::PublicKeyInfo, key::{RetiredSlotId, SlotId}, @@ -184,20 +183,8 @@ fn identity(opts: PluginOptions) -> Result<(), Error> { fn list(all: bool) -> Result<(), Error> { let mut readers = Readers::open()?; - for reader in readers.iter()? { - let mut yubikey = match reader.open() { - Ok(yk) => yk, - Err(e) => { - use std::error::Error; - let reason = if let Some(inner) = e.source() { - format!("{}: {}", e, inner) - } else { - e.to_string() - }; - warn!("Ignoring {}: {}", reader.name(), reason); - continue; - } - }; + for reader in readers.iter()?.filter(yubikey::filter_connected) { + let mut yubikey = reader.open()?; for key in Key::list(&mut yubikey)? { // We only use the retired slots. @@ -294,28 +281,18 @@ fn main() -> Result<(), Error> { eprintln!("make your choice, or press [Esc] or [q] to quit."); eprintln!(); - if Readers::open()?.iter()?.len() == 0 { + if Readers::open()? + .iter()? + .filter(yubikey::filter_connected) + .next() + .is_none() + { eprintln!("⏳ Please insert the YubiKey you want to set up."); }; let mut readers = yubikey::wait_for_readers()?; // Filter out readers we can't connect to. - let readers_list: Vec<_> = readers - .iter()? - .filter(|reader| match reader.open() { - Ok(_) => true, - Err(e) => { - use std::error::Error; - let reason = if let Some(inner) = e.source() { - format!("{}: {}", e, inner) - } else { - e.to_string() - }; - warn!("Ignoring {}: {}", reader.name(), reason); - false - } - }) - .collect(); + let readers_list: Vec<_> = readers.iter()?.filter(yubikey::filter_connected).collect(); let reader_names = readers_list .iter() diff --git a/src/yubikey.rs b/src/yubikey.rs index 1bb9816..f8c8f0c 100644 --- a/src/yubikey.rs +++ b/src/yubikey.rs @@ -7,15 +7,18 @@ use age_core::{ use age_plugin::{identity, Callbacks}; use bech32::{ToBase32, Variant}; use dialoguer::Password; +use log::warn; use secrecy::ExposeSecret; use std::convert::TryInto; use std::fmt; use std::io; +use std::iter; use std::thread::sleep; use std::time::{Duration, SystemTime}; use yubikey_piv::{ certificate::{Certificate, PublicKeyInfo}, key::{decrypt_data, AlgorithmId, RetiredSlotId, SlotId}, + readers::Reader, yubikey::Serial, MgmKey, Readers, YubiKey, }; @@ -30,12 +33,29 @@ use crate::{ const ONE_SECOND: Duration = Duration::from_secs(1); const FIFTEEN_SECONDS: Duration = Duration::from_secs(15); +pub(crate) fn filter_connected(reader: &Reader) -> bool { + match reader.open() { + Ok(_) => true, + Err(e) => { + use std::error::Error; + if let Some(pcsc::Error::RemovedCard) = + e.source().and_then(|inner| inner.downcast_ref()) + { + warn!("Ignoring {}: not connected", reader.name()); + false + } else { + true + } + } + } +} + pub(crate) fn wait_for_readers() -> Result { // Start a 15-second timer waiting for a YubiKey to be inserted (if necessary). let start = SystemTime::now(); loop { let mut readers = Readers::open()?; - if readers.iter()?.len() > 0 { + if readers.iter()?.filter(filter_connected).next().is_some() { break Ok(readers); } @@ -47,7 +67,12 @@ pub(crate) fn wait_for_readers() -> Result { } pub(crate) fn open(serial: Option) -> Result { - if Readers::open()?.iter()?.len() == 0 { + if Readers::open()? + .iter()? + .filter(filter_connected) + .next() + .is_none() + { if let Some(serial) = serial { eprintln!("⏳ Please insert the YubiKey with serial {}.", serial); } else { @@ -55,22 +80,25 @@ pub(crate) fn open(serial: Option) -> Result { } } let mut readers = wait_for_readers()?; - let mut readers_iter = readers.iter()?; + let mut readers_iter = readers.iter()?.filter(filter_connected); // --serial selects the YubiKey to use. If not provided, and more than one YubiKey is // connected, an error is returned. - let yubikey = match (readers_iter.len(), serial) { - (0, _) => unreachable!(), - (1, None) => readers_iter.next().unwrap().open()?, - (1, Some(serial)) => { - let yubikey = readers_iter.next().unwrap().open()?; + let yubikey = match (readers_iter.next(), readers_iter.next(), serial) { + (None, _, _) => unreachable!(), + (Some(reader), None, None) => reader.open()?, + (Some(reader), None, Some(serial)) => { + let yubikey = reader.open()?; if yubikey.serial() != serial { return Err(Error::NoMatchingSerial(serial)); } yubikey } - (_, Some(serial)) => { - let reader = readers_iter + (Some(a), Some(b), Some(serial)) => { + let reader = iter::empty() + .chain(Some(a)) + .chain(Some(b)) + .chain(readers_iter) .find(|reader| match reader.open() { Ok(yk) => yk.serial() == serial, _ => false, @@ -78,7 +106,7 @@ pub(crate) fn open(serial: Option) -> Result { .ok_or(Error::NoMatchingSerial(serial))?; reader.open()? } - (_, None) => return Err(Error::MultipleYubiKeys), + (Some(_), Some(_), None) => return Err(Error::MultipleYubiKeys), }; Ok(yubikey)