diff --git a/Cargo.lock b/Cargo.lock index a0371ba..22589a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -275,6 +275,12 @@ version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +[[package]] +name = "bytecount" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" + [[package]] name = "byteorder" version = "1.4.3" @@ -314,7 +320,7 @@ dependencies = [ [[package]] name = "card-cli" -version = "1.7.0" +version = "1.7.1" dependencies = [ "authenticator", "base64 0.21.2", @@ -339,6 +345,7 @@ dependencies = [ "simpledateformat", "spki 0.7.2", "ssh-agent", + "tabled", "u2f", "x509", "x509-parser", @@ -940,6 +947,12 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -1570,6 +1583,17 @@ dependencies = [ "sha2", ] +[[package]] +name = "papergrid" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ccbe15f2b6db62f9a9871642746427e297b0ceb85f9a7f1ee5ff47d184d0c8" +dependencies = [ + "bytecount", + "fnv", + "unicode-width", +] + [[package]] name = "parking_lot" version = "0.9.0" @@ -1771,6 +1795,30 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote 1.0.33", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote 1.0.33", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.4.3" @@ -2430,6 +2478,30 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tabled" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfe9c3632da101aba5131ed63f9eed38665f8b3c68703a6bb18124835c1a5d22" +dependencies = [ + "papergrid", + "tabled_derive", + "unicode-width", +] + +[[package]] +name = "tabled_derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99f688a08b54f4f02f0a3c382aefdb7884d3d69609f785bd253dc033243e3fe4" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote 1.0.33", + "syn 1.0.109", +] + [[package]] name = "tempfile" version = "3.8.0" diff --git a/Cargo.toml b/Cargo.toml index e0c9078..3de9f8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "card-cli" -version = "1.7.0" +version = "1.7.1" authors = ["Hatter Jiang "] edition = "2018" @@ -33,6 +33,7 @@ x509-parser = "0.15" ssh-agent = { version = "0.2", features = ["agent"] } p256 = { version = "0.13", features = ["pem", "ecdh"] } spki = { version = "0.7", features = ["pem"] } +tabled = "0.14.0" #lazy_static = "1.4.0" #ssh-key = "0.4.0" #ctap-hid-fido2 = "2.1.3" diff --git a/src/cmd_pivsummary.rs b/src/cmd_pivsummary.rs index 6d6c36e..863973d 100644 --- a/src/cmd_pivsummary.rs +++ b/src/cmd_pivsummary.rs @@ -1,13 +1,23 @@ -use clap::{App, ArgMatches, SubCommand}; +use clap::{App, Arg, ArgMatches, SubCommand}; use rust_util::util_clap::{Command, CommandError}; use rust_util::XResult; use spki::der::Encode; +use tabled::{Table, Tabled}; use x509_parser::parse_x509_certificate; use yubikey::{Certificate, YubiKey}; use yubikey::piv::SlotId; use crate::pivutil::get_algorithm_id; +#[derive(Tabled)] +struct PivSlot { + name: String, + id: String, + algorithm: String, + subject: String, +} + + pub struct CommandImpl; impl Command for CommandImpl { @@ -15,9 +25,14 @@ impl Command for CommandImpl { fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("PIV subcommand") + .arg(Arg::with_name("table").long("table").help("Show table")) + .arg(Arg::with_name("all").long("all").help("Show all")) } - fn run(&self, _arg_matches: &ArgMatches, _sub_arg_matches: &ArgMatches) -> CommandError { + fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { + let show_table = sub_arg_matches.is_present("table"); + let show_all = sub_arg_matches.is_present("all"); + let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}"); success!("Name: {}", yk.name()); information!("Version: {}", yk.version()); @@ -42,35 +57,53 @@ impl Command for CommandImpl { Err(e) => failure!("Get PIV keys failed: {}", e) } + let mut piv_slots = vec![]; for slot in yubikey::piv::SLOTS { - print_summary_info(&mut yk, slot).ok(); + print_summary_info(&mut yk, slot, &mut piv_slots, show_all, show_table).ok(); + } + if show_table { + println!("{}", Table::new(piv_slots).to_string()); } Ok(None) } } -fn print_summary_info(yubikey: &mut YubiKey, slot: SlotId) -> XResult<()> { +fn print_summary_info(yubikey: &mut YubiKey, slot: SlotId, piv_slots: &mut Vec, show_all: bool, show_table: bool) -> XResult<()> { let slot_id: u8 = slot.into(); let cert = match Certificate::read(yubikey, slot) { Ok(c) => c, Err(e) => { - warning!("Slot: {:?}, id: {:x}, certificate not found", slot, slot_id); + if show_all { + if show_table { + piv_slots.push(PivSlot { + name: slot.to_string(), + id: format!("{:x}", slot_id), + algorithm: "N/A".to_string(), + subject: "N/A".to_string(), + }); + } else { + warning!("Slot: {:?}, id: {:x}, certificate not found", slot, slot_id); + } + } return simple_error!("error reading certificate in slot {:?}: {}", slot, e); } }; let buf_vec = cert.cert.to_der()?; - let buf: &[u8] = buf_vec.as_ref(); - if buf.is_empty() { - warning!("Slot: {:?}, id: {:x}, certificate buffer empty", slot, slot_id); + let algorithm_id = get_algorithm_id(&cert.cert.tbs_certificate.subject_public_key_info) + .map(|aid| format!("{:?}", aid)) + .unwrap_or_else(|e| format!("Error: {}", e)); + let cert_subject = match parse_x509_certificate(&buf_vec) { + Ok((_rem, cert)) => cert.subject.to_string(), + _ => cert.cert.tbs_certificate.subject.to_string(), + }; + if show_table { + piv_slots.push(PivSlot { + name: slot.to_string(), + id: format!("{:x}", slot_id), + algorithm: algorithm_id, + subject: cert_subject, + }); } else { - let slot_id: u8 = slot.into(); - let algorithm_id = get_algorithm_id(&cert.cert.tbs_certificate.subject_public_key_info) - .map(|aid| format!("{:?}", aid)) - .unwrap_or_else(|e| format!("Error: {}", e)); - let cert_subject = match parse_x509_certificate(buf) { - Ok((_rem, cert)) => cert.subject.to_string(), - _ => cert.cert.tbs_certificate.subject.to_string(), - }; success!("Slot: {:?}, id: {:x}, algorithm: {}, subject: {}", slot, slot_id, algorithm_id, cert_subject); }