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:
Generated
+1
@@ -60,6 +60,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"man",
|
"man",
|
||||||
"p256",
|
"p256",
|
||||||
|
"pcsc",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"secrecy",
|
"secrecy",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user