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.
This commit is contained in:
Jack Grigg
2021-04-29 00:04:37 +12:00
parent 2a013fc018
commit 458a09125f
3 changed files with 73 additions and 46 deletions
+3 -3
View File
@@ -49,18 +49,18 @@ fn main() {
Flag::new() Flag::new()
.short("-i") .short("-i")
.long("--identity") .long("--identity")
.help("Print the identity stored in a YubiKey slot."), .help("Print identities stored in connected YubiKeys."),
) )
.flag( .flag(
Flag::new() Flag::new()
.short("-l") .short("-l")
.long("--list") .long("--list")
.help("List all age identities in connected YubiKeys."), .help("List recipients for age identities in connected YubiKeys."),
) )
.flag( .flag(
Flag::new() Flag::new()
.long("--list-all") .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(
Flag::new() Flag::new()
-9
View File
@@ -12,10 +12,8 @@ pub enum Error {
InvalidTouchPolicy(String), InvalidTouchPolicy(String),
Io(io::Error), Io(io::Error),
MultipleCommands, MultipleCommands,
MultipleIdentities,
MultipleYubiKeys, MultipleYubiKeys,
NoEmptySlots(Serial), NoEmptySlots(Serial),
NoIdentities,
NoMatchingSerial(Serial), NoMatchingSerial(Serial),
SlotHasNoIdentity(RetiredSlotId), SlotHasNoIdentity(RetiredSlotId),
SlotIsNotEmpty(RetiredSlotId), SlotIsNotEmpty(RetiredSlotId),
@@ -64,10 +62,6 @@ impl fmt::Debug for Error {
f, f,
"Only one of --generate, --identity, --list, --list-all can be specified." "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!( Error::MultipleYubiKeys => writeln!(
f, f,
"Multiple YubiKeys are plugged in. Use --serial to select a single YubiKey." "Multiple YubiKeys are plugged in. Use --serial to select a single YubiKey."
@@ -75,9 +69,6 @@ impl fmt::Debug for Error {
Error::NoEmptySlots(serial) => { Error::NoEmptySlots(serial) => {
writeln!(f, "YubiKey with serial {} has no empty slots.", 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) => { Error::NoMatchingSerial(serial) => {
writeln!(f, "Could not find YubiKey with serial {}.", serial)? writeln!(f, "Could not find YubiKey with serial {}.", serial)?
} }
+70 -34
View File
@@ -72,13 +72,16 @@ struct PluginOptions {
#[options(help = "Generate a new YubiKey identity.")] #[options(help = "Generate a new YubiKey identity.")]
generate: bool, generate: bool,
#[options(help = "Print the identity stored in a YubiKey slot.")] #[options(help = "Print identities stored in connected YubiKeys.")]
identity: bool, identity: bool,
#[options(help = "List all age identities in connected YubiKeys.")] #[options(help = "List recipients for age identities in connected YubiKeys.")]
list: bool, 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, list_all: bool,
#[options( #[options(
@@ -159,8 +162,12 @@ fn generate(flags: PluginFlags) -> Result<(), Error> {
Ok(()) Ok(())
} }
fn identity(flags: PluginFlags) -> Result<(), Error> { fn print_single(
let mut yubikey = yubikey::open(flags.serial)?; serial: Option<Serial>,
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| { let mut keys = Key::list(&mut yubikey)?.into_iter().filter_map(|key| {
// - We only use the retired slots. // - 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 { let (key, slot, recipient) = keys
keys.find(|(_, s, _)| s == &slot) .find(|(_, s, _)| s == &slot)
.ok_or(Error::SlotHasNoIdentity(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 stub = yubikey::Stub::new(yubikey.serial(), slot, &recipient); let stub = yubikey::Stub::new(yubikey.serial(), slot, &recipient);
let metadata = x509_parser::parse_x509_certificate(key.certificate().as_ref()) 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)) .and_then(|(_, cert)| util::Metadata::extract(&mut yubikey, slot, &cert, true))
.unwrap(); .unwrap();
util::print_identity(stub, recipient, metadata); printer(stub, recipient, metadata);
Ok(()) Ok(())
} }
fn list(all: bool) -> Result<(), Error> { fn print_multiple(
kind: &str,
serial: Option<Serial>,
all: bool,
printer: impl Fn(yubikey::Stub, p256::Recipient, util::Metadata),
) -> Result<(), Error> {
let mut readers = Readers::open()?; let mut readers = Readers::open()?;
let mut printed = 0;
for reader in readers.iter()?.filter(yubikey::filter_connected) { for reader in readers.iter()?.filter(yubikey::filter_connected) {
let mut yubikey = reader.open()?; let mut yubikey = reader.open()?;
if let Some(serial) = serial {
if yubikey.serial() != serial {
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.
@@ -230,6 +228,7 @@ fn list(all: bool) -> Result<(), Error> {
_ => continue, _ => continue,
}; };
let stub = yubikey::Stub::new(yubikey.serial(), slot, &recipient);
let metadata = match x509_parser::parse_x509_certificate(key.certificate().as_ref()) let metadata = match x509_parser::parse_x509_certificate(key.certificate().as_ref())
.ok() .ok()
.and_then(|(_, cert)| util::Metadata::extract(&mut yubikey, slot, &cert, all)) .and_then(|(_, cert)| util::Metadata::extract(&mut yubikey, slot, &cert, all))
@@ -238,16 +237,46 @@ fn list(all: bool) -> Result<(), Error> {
None => continue, None => continue,
}; };
println!("{}", metadata); printer(stub, recipient, metadata);
println!("{}", recipient.to_string()); printed += 1;
println!(); println!();
} }
println!(); println!();
} }
if printed > 1 {
eprintln!(
"Generated {} for {} slots. If you intended to select a slot, use --slot.",
kind, printed,
);
}
Ok(()) 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> { fn main() -> Result<(), Error> {
env_logger::builder() env_logger::builder()
.format_timestamp(None) .format_timestamp(None)
@@ -281,9 +310,9 @@ fn main() -> Result<(), Error> {
} else if opts.identity { } else if opts.identity {
identity(opts.try_into()?) identity(opts.try_into()?)
} else if opts.list { } else if opts.list {
list(false) list(opts.try_into()?, false)
} else if opts.list_all { } else if opts.list_all {
list(true) list(opts.try_into()?, true)
} else { } else {
let flags: PluginFlags = opts.try_into()?; let flags: PluginFlags = opts.try_into()?;
@@ -548,6 +577,13 @@ fn main() -> Result<(), Error> {
file_name, file_name,
); );
eprintln!(); 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."); eprintln!("💭 Remember: everything breaks, have a backup plan for when this YubiKey does.");
Ok(()) Ok(())