Files
card-cli/src/cmd_piv.rs
2022-03-27 17:32:23 +08:00

127 lines
4.9 KiB
Rust

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: <none> {}", e),
}
match yk.cccid() {
Ok(cccid) => success!("CCCID: {}",cccid.to_string()),
Err(e) => warning!("CCCID: <none> {}", e),
}
match yk.get_pin_retries() {
Ok(pin_retries) => success!("PIN retries: {}",pin_retries),
Err(e) => warning!("PIN retries: <none> {}", 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(())
}