use std::time::Duration; use chrono::Local; use clap::{App, Arg, ArgMatches, SubCommand}; use digest::Digest; use pem::Pem; use rust_util::util_clap::{Command, CommandError}; use rust_util::util_msg::MessageType; use rust_util::XResult; use sha2::Sha256; use x509_parser::parse_x509_certificate; use yubikey::{Certificate, YubiKey}; use yubikey::piv::SlotId; pub struct CommandImpl; impl Command for CommandImpl { fn name(&self) -> &str { "piv" } fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("PIV subcommand") .arg(Arg::with_name("detail").long("detail").help("Detail output")) .arg(Arg::with_name("show-config").long("show-config").help("Show config output")) // .arg(Arg::with_name("json").long("json").help("JSON output")) } fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { let detail_output = sub_arg_matches.is_present("detail"); let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}"); success!("Name: {}", yk.name()); success!("Version: {}", yk.version()); success!("Serial: {}", yk.serial()); match yk.chuid() { Ok(chuid) => success!("CHUID: {}",chuid.to_string()), Err(e) => warning!("CHUID: {}", e), } match yk.cccid() { Ok(cccid) => success!("CCCID: {}",cccid.to_string()), Err(e) => warning!("CCCID: {}", e), } match yk.get_pin_retries() { Ok(pin_retries) => success!("PIN retries: {}",pin_retries), Err(e) => warning!("PIN retries: {}", e), } if sub_arg_matches.is_present("show-config") { let config = yk.config(); information!("Config: {:#?}", config); } match yk.piv_keys() { Ok(keys) => { information!("Found {} PIV keys", keys.len()); if detail_output { keys.iter().for_each(|k| information!("Found key: {:?}", k)); } } Err(e) => failure!("Get PIV keys failed: {}", e) } for slot in yubikey::piv::SLOTS.iter().cloned() { print_cert_info(&mut yk, slot, detail_output).ok(); } Ok(None) } } fn print_cert_info(yubikey: &mut YubiKey, slot: SlotId, detail_output: bool) -> XResult<()> { let cert = match Certificate::read(yubikey, slot) { Ok(c) => c, Err(e) => { let slot_id: u8 = slot.into(); debugging!("error reading certificate in slot {:?}, id: {:x}: {}", slot, slot_id, e); return simple_error!("error reading certificate in slot {:?}: {}", slot, e); } }; let buf = cert.into_buffer(); if !buf.is_empty() { information!("{}", "-".repeat(88)); let fingerprint_sha256 = Sha256::digest(&buf); let slot_id: u8 = slot.into(); success!("Slot: {:?}, id: {:x}", slot, slot_id); let cert_pem_obj = Pem { tag: String::from("CERTIFICATE"), contents: buf.to_vec(), }; if detail_output { information!("{}", pem::encode(&cert_pem_obj).trim()); } else { rust_util::util_msg::when(MessageType::DEBUG, || { debugging!("{}", pem::encode(&cert_pem_obj).trim()); }); } match parse_x509_certificate(&buf) { Ok((_rem, cert)) => { success!("Algorithm: {}", cert.tbs_certificate.subject_pki.algorithm.algorithm); success!("Subject: {}", cert.tbs_certificate.subject); success!("Issuer: {}", cert.tbs_certificate.issuer); success!("Fingerprint(SHA256): {}", hex::encode(fingerprint_sha256)); success!("Not Before: {}", cert.tbs_certificate.validity.not_before.to_rfc2822()); let mut not_after_desc = String::new(); let not_after_timestamp = cert.tbs_certificate.validity.not_after.timestamp(); let now_timestamp = Local::now().timestamp(); if not_after_timestamp < now_timestamp { let expired_time = simpledateformat::format_human(Duration::from_secs((now_timestamp - not_after_timestamp) as u64)); not_after_desc.push_str(&format!("(EXPIRED {})", expired_time)); } else { let valid_time = simpledateformat::format_human(Duration::from_secs((not_after_timestamp - now_timestamp) as u64)); not_after_desc.push_str(&format!("(left {})", valid_time)); } success!("Not After: {} {}", cert.tbs_certificate.validity.not_after.to_rfc2822(), not_after_desc); } _ => { warning!("Failed to parse certificate"); return Ok(()); } }; } Ok(()) }