diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 0b3041b..3be5a6e 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -16,5 +16,9 @@ keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"] gumdrop = "0.7" env_logger = "0.7" lazy_static = "1" +log = "0.4" +sha2 = "0.8" +subtle-encoding = "0.5" termcolor = "1" +x509-parser = "0.6" yubikey-piv = { version = "0.0.3", path = ".." } diff --git a/cli/src/commands/status.rs b/cli/src/commands/status.rs index 19df839..9c8d554 100644 --- a/cli/src/commands/status.rs +++ b/cli/src/commands/status.rs @@ -4,7 +4,9 @@ use crate::terminal::STDOUT; use gumdrop::Options; use std::io::{self, Write}; use termcolor::{ColorSpec, StandardStreamLock, WriteColor}; -use yubikey_piv::YubiKey; +use yubikey_piv::{key::*, YubiKey}; + +use crate::print_cert_info; // String to use for `None` const NONE_STR: &str = ""; @@ -37,6 +39,10 @@ impl StatusCmd { self.attr(&mut s, "PIN retries", yk.get_pin_retries().unwrap()) .unwrap(); + + for slot in SLOTS.iter().cloned() { + print_cert_info(&mut yk, slot, &mut s).unwrap(); + } } /// Print a status attribute diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 04fade7..a9cc863 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -12,3 +12,81 @@ pub mod terminal; pub mod commands; + +use log::debug; +use sha2::{Digest, Sha256}; +use std::io::{self, Write}; +use std::str; +use subtle_encoding::hex; +use termcolor::{ColorSpec, StandardStreamLock, WriteColor}; +use x509_parser::parse_x509_der; +use yubikey_piv::{certificate::Certificate, key::*, YubiKey}; + +///Write information about certificate found in slot a la yubico-piv-tool output. +pub fn print_cert_info( + yubikey: &mut YubiKey, + slot: SlotId, + stream: &mut StandardStreamLock<'_>, +) -> Result<(), io::Error> { + let cert = match Certificate::read(yubikey, slot) { + Ok(c) => c, + Err(e) => { + debug!("error reading certificate in slot {:?}: {}", slot, e); + return Ok(()); + } + }; + let buf = cert.into_buffer(); + + if !buf.is_empty() { + let fingerprint = Sha256::digest(&buf); + let slot_id: u8 = slot.into(); + print_cert_attr(stream, "Slot", format!("{:x}", slot_id))?; + match parse_x509_der(&buf) { + Ok((_rem, cert)) => { + print_cert_attr( + stream, + "Algorithm", + cert.tbs_certificate.subject_pki.algorithm.algorithm, + )?; + + print_cert_attr(stream, "Subject", cert.tbs_certificate.subject)?; + print_cert_attr(stream, "Issuer", cert.tbs_certificate.issuer)?; + print_cert_attr( + stream, + "Fingerprint", + str::from_utf8(hex::encode(fingerprint).as_slice()).unwrap(), + )?; + print_cert_attr( + stream, + "Not Before", + cert.tbs_certificate.validity.not_before.asctime(), + )?; + print_cert_attr( + stream, + "Not After", + cert.tbs_certificate.validity.not_after.asctime(), + )?; + } + _ => { + println!("Failed to parse certificate"); + return Ok(()); + } + }; + } + + Ok(()) +} + +/// Print a status attribute +fn print_cert_attr( + stream: &mut StandardStreamLock<'_>, + name: &str, + value: impl ToString, +) -> Result<(), io::Error> { + stream.set_color(ColorSpec::new().set_bold(true))?; + write!(stream, "{:>12}:", name)?; + stream.reset()?; + writeln!(stream, " {}", value.to_string())?; + stream.flush()?; + Ok(()) +}