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:
@@ -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()
|
||||||
|
|||||||
@@ -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
@@ -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(())
|
||||||
|
|||||||
Reference in New Issue
Block a user