diff --git a/Cargo.lock b/Cargo.lock index cf9f1e1..49be721 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -487,7 +487,7 @@ dependencies = [ [[package]] name = "card-cli" -version = "1.10.6" +version = "1.10.7" dependencies = [ "authenticator 0.3.1", "base64 0.21.7", diff --git a/Cargo.toml b/Cargo.toml index e3ee431..4e87798 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "card-cli" -version = "1.10.6" +version = "1.10.7" authors = ["Hatter Jiang "] edition = "2018" diff --git a/src/cmd_pivsummary.rs b/src/cmd_pivsummary.rs index d340add..20c3907 100644 --- a/src/cmd_pivsummary.rs +++ b/src/cmd_pivsummary.rs @@ -1,18 +1,20 @@ use clap::{App, Arg, ArgMatches, SubCommand}; use rust_util::util_clap::{Command, CommandError}; -use rust_util::XResult; +use rust_util::{util_msg, XResult}; +use serde::Serialize; +use serde_json::{Map, Value}; use spki::der::Encode; -use tabled::{Table, Tabled}; use tabled::settings::Style; +use tabled::{Table, Tabled}; use x509_parser::parse_x509_certificate; -use yubikey::{Certificate, YubiKey}; use yubikey::piv::{metadata, SlotId}; +use yubikey::{Certificate, YubiKey}; -use crate::pivutil::{get_algorithm_id_by_certificate, ORDERED_SLOTS, ToStr}; +use crate::pivutil::{get_algorithm_id_by_certificate, ToStr, ORDERED_SLOTS}; const NA: &str = "N/A"; -#[derive(Tabled)] +#[derive(Tabled, Serialize)] struct PivSlot { name: String, id: String, @@ -33,6 +35,7 @@ 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("json").long("json").help("JSON output")) .arg(Arg::with_name("all").long("all").help("Show all")) .arg(Arg::with_name("ordered").long("ordered").help("Show ordered")) } @@ -41,45 +44,69 @@ impl Command for CommandImpl { let show_table = sub_arg_matches.is_present("table"); let show_all = sub_arg_matches.is_present("all"); let show_ordered = sub_arg_matches.is_present("ordered"); + let json_output = sub_arg_matches.is_present("json"); + if json_output { util_msg::set_logger_std_out(false); } + let mut output = Map::new(); let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}"); + success!("Name: {}", yk.name()); information!("Version: {}", yk.version()); information!("Serial: {}", yk.serial()); + output.insert("name".to_string(), Value::String(yk.name().to_string())); + output.insert("version".to_string(), Value::String(yk.version().to_string())); + output.insert("serial".to_string(), Value::String(yk.serial().to_string())); + match yk.chuid() { - Ok(chuid) => information!("CHUID: {}",chuid.to_string()), + Ok(chuid) => { + information!("CHUID: {}",chuid.to_string()); + output.insert("chuid".to_string(), Value::String(chuid.to_string())); + } Err(e) => warning!("CHUID: {}", e), } match yk.cccid() { - Ok(cccid) => information!("CCCID: {}",cccid.to_string()), + Ok(cccid) => { + information!("CCCID: {}",cccid.to_string()); + output.insert("cccid".to_string(), Value::String(cccid.to_string())); + } Err(e) => warning!("CCCID: {}", e), } match yk.get_pin_retries() { - Ok(pin_retries) => information!("PIN retries: {}",pin_retries), + Ok(pin_retries) => { + information!("PIN retries: {}",pin_retries); + output.insert("pin_retries".to_string(), Value::String(pin_retries.to_string())); + } Err(e) => warning!("PIN retries: {}", e), } match yk.piv_keys() { - Ok(keys) => { - information!("Found {} PIV keys of {}", keys.len(), ORDERED_SLOTS.len()); - } + Ok(keys) => information!("Found {} PIV keys of {}", keys.len(), ORDERED_SLOTS.len()), Err(e) => failure!("Get PIV keys failed: {}", e) } let mut piv_slots = vec![]; for slot in iff!(show_ordered, ORDERED_SLOTS, yubikey::piv::SLOTS) { - print_summary_info(&mut yk, slot, &mut piv_slots, show_all, show_table).ok(); + print_summary_info(&mut yk, slot, &mut piv_slots, show_all, show_table, json_output).ok(); } + if show_table { let mut table = Table::new(piv_slots); table.with(Style::rounded()); println!("{}", table); + } else if json_output { + let piv_slots_json = serde_json::to_string(&piv_slots).unwrap(); + let piv_slots_values: Vec = serde_json::from_str(&piv_slots_json).unwrap(); + output.insert("piv_slots".to_string(), Value::Array(piv_slots_values)); } + if json_output { + println!("{}", serde_json::to_string_pretty(&output).unwrap()); + } + Ok(None) } } -fn print_summary_info(yubikey: &mut YubiKey, slot: SlotId, piv_slots: &mut Vec, show_all: bool, show_table: bool) -> XResult<()> { +fn print_summary_info(yubikey: &mut YubiKey, slot: SlotId, piv_slots: &mut Vec, show_all: bool, show_table: bool, json_output: bool) -> XResult<()> { let slot_id: u8 = slot.into(); let mut origin = NA.to_string(); let mut retries = NA.to_string(); @@ -101,7 +128,7 @@ fn print_summary_info(yubikey: &mut YubiKey, slot: SlotId, piv_slots: &mut Vec

c, Err(e) => { if show_all { - if show_table { + if show_table || json_output { piv_slots.push(PivSlot { name: slot.to_string(), id: format!("{:x}", slot_id), @@ -127,7 +154,7 @@ fn print_summary_info(yubikey: &mut YubiKey, slot: SlotId, piv_slots: &mut Vec

cert.subject.to_string(), _ => cert.cert.tbs_certificate.subject.to_string(), }; - if show_table { + if show_table || json_output { piv_slots.push(PivSlot { name: slot.to_string(), id: format!("{:x}", slot_id),