Reliably ignore PIV devices that are not connected

This is primarily to ignore smart card readers that don't have cards
plugged in.
This commit is contained in:
Jack Grigg
2021-04-14 00:33:39 +12:00
parent 856a73dc90
commit 50b873c89f
4 changed files with 50 additions and 43 deletions
Generated
+1
View File
@@ -60,6 +60,7 @@ dependencies = [
"log", "log",
"man", "man",
"p256", "p256",
"pcsc",
"rand 0.7.3", "rand 0.7.3",
"secrecy", "secrecy",
"sha2", "sha2",
+1
View File
@@ -33,6 +33,7 @@ gumdrop = "0.8"
hex = "0.4" hex = "0.4"
log = "0.4" log = "0.4"
p256 = { version = "0.7", features = ["ecdh"] } p256 = { version = "0.7", features = ["ecdh"] }
pcsc = "2.4"
rand = "0.7" rand = "0.7"
secrecy = "0.7" secrecy = "0.7"
sha2 = "0.9" sha2 = "0.9"
+9 -32
View File
@@ -1,7 +1,6 @@
use age_plugin::run_state_machine; use age_plugin::run_state_machine;
use dialoguer::{Confirm, Select}; use dialoguer::{Confirm, Select};
use gumdrop::Options; use gumdrop::Options;
use log::warn;
use yubikey_piv::{ use yubikey_piv::{
certificate::PublicKeyInfo, certificate::PublicKeyInfo,
key::{RetiredSlotId, SlotId}, key::{RetiredSlotId, SlotId},
@@ -184,20 +183,8 @@ fn identity(opts: PluginOptions) -> Result<(), Error> {
fn list(all: bool) -> Result<(), Error> { fn list(all: bool) -> Result<(), Error> {
let mut readers = Readers::open()?; let mut readers = Readers::open()?;
for reader in readers.iter()? { for reader in readers.iter()?.filter(yubikey::filter_connected) {
let mut yubikey = match reader.open() { let mut yubikey = 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 key in Key::list(&mut yubikey)? { for key in Key::list(&mut yubikey)? {
// We only use the retired slots. // 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!("make your choice, or press [Esc] or [q] to quit.");
eprintln!(); 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."); eprintln!("⏳ Please insert the YubiKey you want to set up.");
}; };
let mut readers = yubikey::wait_for_readers()?; let mut readers = yubikey::wait_for_readers()?;
// Filter out readers we can't connect to. // Filter out readers we can't connect to.
let readers_list: Vec<_> = readers let readers_list: Vec<_> = readers.iter()?.filter(yubikey::filter_connected).collect();
.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 reader_names = readers_list let reader_names = readers_list
.iter() .iter()
+39 -11
View File
@@ -7,15 +7,18 @@ use age_core::{
use age_plugin::{identity, Callbacks}; use age_plugin::{identity, Callbacks};
use bech32::{ToBase32, Variant}; use bech32::{ToBase32, Variant};
use dialoguer::Password; use dialoguer::Password;
use log::warn;
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
use std::convert::TryInto; use std::convert::TryInto;
use std::fmt; use std::fmt;
use std::io; use std::io;
use std::iter;
use std::thread::sleep; use std::thread::sleep;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use yubikey_piv::{ use yubikey_piv::{
certificate::{Certificate, PublicKeyInfo}, certificate::{Certificate, PublicKeyInfo},
key::{decrypt_data, AlgorithmId, RetiredSlotId, SlotId}, key::{decrypt_data, AlgorithmId, RetiredSlotId, SlotId},
readers::Reader,
yubikey::Serial, yubikey::Serial,
MgmKey, Readers, YubiKey, MgmKey, Readers, YubiKey,
}; };
@@ -30,12 +33,29 @@ use crate::{
const ONE_SECOND: Duration = Duration::from_secs(1); const ONE_SECOND: Duration = Duration::from_secs(1);
const FIFTEEN_SECONDS: Duration = Duration::from_secs(15); 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<Readers, Error> { pub(crate) fn wait_for_readers() -> Result<Readers, Error> {
// Start a 15-second timer waiting for a YubiKey to be inserted (if necessary). // Start a 15-second timer waiting for a YubiKey to be inserted (if necessary).
let start = SystemTime::now(); let start = SystemTime::now();
loop { loop {
let mut readers = Readers::open()?; let mut readers = Readers::open()?;
if readers.iter()?.len() > 0 { if readers.iter()?.filter(filter_connected).next().is_some() {
break Ok(readers); break Ok(readers);
} }
@@ -47,7 +67,12 @@ pub(crate) fn wait_for_readers() -> Result<Readers, Error> {
} }
pub(crate) fn open(serial: Option<Serial>) -> Result<YubiKey, Error> { pub(crate) fn open(serial: Option<Serial>) -> Result<YubiKey, Error> {
if Readers::open()?.iter()?.len() == 0 { if Readers::open()?
.iter()?
.filter(filter_connected)
.next()
.is_none()
{
if let Some(serial) = serial { if let Some(serial) = serial {
eprintln!("⏳ Please insert the YubiKey with serial {}.", serial); eprintln!("⏳ Please insert the YubiKey with serial {}.", serial);
} else { } else {
@@ -55,22 +80,25 @@ pub(crate) fn open(serial: Option<Serial>) -> Result<YubiKey, Error> {
} }
} }
let mut readers = wait_for_readers()?; 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 // --serial selects the YubiKey to use. If not provided, and more than one YubiKey is
// connected, an error is returned. // connected, an error is returned.
let yubikey = match (readers_iter.len(), serial) { let yubikey = match (readers_iter.next(), readers_iter.next(), serial) {
(0, _) => unreachable!(), (None, _, _) => unreachable!(),
(1, None) => readers_iter.next().unwrap().open()?, (Some(reader), None, None) => reader.open()?,
(1, Some(serial)) => { (Some(reader), None, Some(serial)) => {
let yubikey = readers_iter.next().unwrap().open()?; let yubikey = reader.open()?;
if yubikey.serial() != serial { if yubikey.serial() != serial {
return Err(Error::NoMatchingSerial(serial)); return Err(Error::NoMatchingSerial(serial));
} }
yubikey yubikey
} }
(_, Some(serial)) => { (Some(a), Some(b), Some(serial)) => {
let reader = readers_iter let reader = iter::empty()
.chain(Some(a))
.chain(Some(b))
.chain(readers_iter)
.find(|reader| match reader.open() { .find(|reader| match reader.open() {
Ok(yk) => yk.serial() == serial, Ok(yk) => yk.serial() == serial,
_ => false, _ => false,
@@ -78,7 +106,7 @@ pub(crate) fn open(serial: Option<Serial>) -> Result<YubiKey, Error> {
.ok_or(Error::NoMatchingSerial(serial))?; .ok_or(Error::NoMatchingSerial(serial))?;
reader.open()? reader.open()?
} }
(_, None) => return Err(Error::MultipleYubiKeys), (Some(_), Some(_), None) => return Err(Error::MultipleYubiKeys),
}; };
Ok(yubikey) Ok(yubikey)