From 458a09125fe6889661b485aed658d6746605dd59 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 29 Apr 2021 00:04:37 +1200 Subject: [PATCH] Make --list{-all} and --identity behave the same way - Both commands print all slots from all connected YubiKeys by default. - If --serial is specified (without --slot) then print all slots in that YubiKey. - If --slot is specified then then instead print a single slot (requiring --serial if necessary to select a single YubiKey). Closes str4d/age-plugin-yubikey#26. --- examples/generate-docs.rs | 6 +-- src/error.rs | 9 ---- src/main.rs | 104 +++++++++++++++++++++++++------------- 3 files changed, 73 insertions(+), 46 deletions(-) diff --git a/examples/generate-docs.rs b/examples/generate-docs.rs index 4e5b609..99c42d2 100644 --- a/examples/generate-docs.rs +++ b/examples/generate-docs.rs @@ -49,18 +49,18 @@ fn main() { Flag::new() .short("-i") .long("--identity") - .help("Print the identity stored in a YubiKey slot."), + .help("Print identities stored in connected YubiKeys."), ) .flag( Flag::new() .short("-l") .long("--list") - .help("List all age identities in connected YubiKeys."), + .help("List recipients for age identities in connected YubiKeys."), ) .flag( Flag::new() .long("--list-all") - .help("List all YubiKey keys that are compatible with age."), + .help("List recipients for all YubiKey keys that are compatible with age."), ) .flag( Flag::new() diff --git a/src/error.rs b/src/error.rs index 2f5ed81..c71ba65 100644 --- a/src/error.rs +++ b/src/error.rs @@ -12,10 +12,8 @@ pub enum Error { InvalidTouchPolicy(String), Io(io::Error), MultipleCommands, - MultipleIdentities, MultipleYubiKeys, NoEmptySlots(Serial), - NoIdentities, NoMatchingSerial(Serial), SlotHasNoIdentity(RetiredSlotId), SlotIsNotEmpty(RetiredSlotId), @@ -64,10 +62,6 @@ impl fmt::Debug for Error { f, "Only one of --generate, --identity, --list, --list-all can be specified." )?, - Error::MultipleIdentities => writeln!( - f, - "This YubiKey has multiple age identities. Use --slot to select a single identity." - )?, Error::MultipleYubiKeys => writeln!( f, "Multiple YubiKeys are plugged in. Use --serial to select a single YubiKey." @@ -75,9 +69,6 @@ impl fmt::Debug for Error { Error::NoEmptySlots(serial) => { writeln!(f, "YubiKey with serial {} has no empty slots.", serial)? } - Error::NoIdentities => { - writeln!(f, "This YubiKey does not contain any age identities.")? - } Error::NoMatchingSerial(serial) => { writeln!(f, "Could not find YubiKey with serial {}.", serial)? } diff --git a/src/main.rs b/src/main.rs index 06f0aa1..0e23b99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,13 +72,16 @@ struct PluginOptions { #[options(help = "Generate a new YubiKey identity.")] generate: bool, - #[options(help = "Print the identity stored in a YubiKey slot.")] + #[options(help = "Print identities stored in connected YubiKeys.")] identity: bool, - #[options(help = "List all age identities in connected YubiKeys.")] + #[options(help = "List recipients for age identities in connected YubiKeys.")] list: bool, - #[options(help = "List all YubiKey keys that are compatible with age.", no_short)] + #[options( + help = "List recipients for all YubiKey keys that are compatible with age.", + no_short + )] list_all: bool, #[options( @@ -159,8 +162,12 @@ fn generate(flags: PluginFlags) -> Result<(), Error> { Ok(()) } -fn identity(flags: PluginFlags) -> Result<(), Error> { - let mut yubikey = yubikey::open(flags.serial)?; +fn print_single( + serial: Option, + slot: RetiredSlotId, + printer: impl Fn(yubikey::Stub, p256::Recipient, util::Metadata), +) -> Result<(), Error> { + let mut yubikey = yubikey::open(serial)?; let mut keys = Key::list(&mut yubikey)?.into_iter().filter_map(|key| { // - We only use the retired slots. @@ -173,29 +180,9 @@ fn identity(flags: PluginFlags) -> Result<(), Error> { } }); - let (key, slot, recipient) = if let Some(slot) = flags.slot { - keys.find(|(_, s, _)| s == &slot) - .ok_or(Error::SlotHasNoIdentity(slot)) - } else { - let mut keys = keys.filter(|(key, _, _)| { - let cert = x509_parser::parse_x509_certificate(key.certificate().as_ref()) - .map(|(_, cert)| cert) - .ok(); - match cert - .as_ref() - .and_then(|cert| cert.subject().iter_organization().next()) - { - Some(org) => org.as_str() == Ok(BINARY_NAME), - _ => false, - } - }); - match (keys.next(), keys.next()) { - (None, None) => Err(Error::NoIdentities), - (Some(key), None) => Ok(key), - (Some(_), Some(_)) => Err(Error::MultipleIdentities), - (None, Some(_)) => unreachable!(), - } - }?; + let (key, slot, recipient) = keys + .find(|(_, s, _)| s == &slot) + .ok_or(Error::SlotHasNoIdentity(slot))?; let stub = yubikey::Stub::new(yubikey.serial(), slot, &recipient); let metadata = x509_parser::parse_x509_certificate(key.certificate().as_ref()) @@ -203,16 +190,27 @@ fn identity(flags: PluginFlags) -> Result<(), Error> { .and_then(|(_, cert)| util::Metadata::extract(&mut yubikey, slot, &cert, true)) .unwrap(); - util::print_identity(stub, recipient, metadata); + printer(stub, recipient, metadata); Ok(()) } -fn list(all: bool) -> Result<(), Error> { +fn print_multiple( + kind: &str, + serial: Option, + all: bool, + printer: impl Fn(yubikey::Stub, p256::Recipient, util::Metadata), +) -> Result<(), Error> { let mut readers = Readers::open()?; + let mut printed = 0; for reader in readers.iter()?.filter(yubikey::filter_connected) { let mut yubikey = reader.open()?; + if let Some(serial) = serial { + if yubikey.serial() != serial { + continue; + } + } for key in Key::list(&mut yubikey)? { // We only use the retired slots. @@ -230,6 +228,7 @@ fn list(all: bool) -> Result<(), Error> { _ => continue, }; + let stub = yubikey::Stub::new(yubikey.serial(), slot, &recipient); let metadata = match x509_parser::parse_x509_certificate(key.certificate().as_ref()) .ok() .and_then(|(_, cert)| util::Metadata::extract(&mut yubikey, slot, &cert, all)) @@ -238,16 +237,46 @@ fn list(all: bool) -> Result<(), Error> { None => continue, }; - println!("{}", metadata); - println!("{}", recipient.to_string()); + printer(stub, recipient, metadata); + printed += 1; println!(); } println!(); } + if printed > 1 { + eprintln!( + "Generated {} for {} slots. If you intended to select a slot, use --slot.", + kind, printed, + ); + } Ok(()) } +fn print_details( + kind: &str, + flags: PluginFlags, + all: bool, + printer: impl Fn(yubikey::Stub, p256::Recipient, util::Metadata), +) -> Result<(), Error> { + if let Some(slot) = flags.slot { + print_single(flags.serial, slot, printer) + } else { + print_multiple(kind, flags.serial, all, printer) + } +} + +fn identity(flags: PluginFlags) -> Result<(), Error> { + print_details("identities", flags, false, util::print_identity) +} + +fn list(flags: PluginFlags, all: bool) -> Result<(), Error> { + print_details("recipients", flags, all, |_, recipient, metadata| { + println!("{}", metadata); + println!("{}", recipient.to_string()); + }) +} + fn main() -> Result<(), Error> { env_logger::builder() .format_timestamp(None) @@ -281,9 +310,9 @@ fn main() -> Result<(), Error> { } else if opts.identity { identity(opts.try_into()?) } else if opts.list { - list(false) + list(opts.try_into()?, false) } else if opts.list_all { - list(true) + list(opts.try_into()?, true) } else { let flags: PluginFlags = opts.try_into()?; @@ -548,6 +577,13 @@ fn main() -> Result<(), Error> { file_name, ); eprintln!(); + eprintln!("- Recreate the recipient:"); + eprintln!( + " $ age-plugin-yubikey -l --serial {} --slot {}", + stub.serial, + util::slot_to_ui(&stub.slot), + ); + eprintln!(); eprintln!("💭 Remember: everything breaks, have a backup plan for when this YubiKey does."); Ok(())