Merge pull request #72 from iqlusioninc/status-command

cli: add `status` command
This commit is contained in:
Tony Arcieri
2019-12-09 19:29:07 -08:00
committed by GitHub
12 changed files with 189 additions and 34 deletions
Generated
+10
View File
@@ -710,6 +710,14 @@ name = "subtle"
version = "2.2.2" version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "subtle-encoding"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.11" version = "1.0.11"
@@ -856,6 +864,7 @@ dependencies = [
"secrecy 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "secrecy 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"subtle-encoding 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"x509-parser 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "x509-parser 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@@ -972,6 +981,7 @@ dependencies = [
"checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" "checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" "checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
"checksum subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941" "checksum subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941"
"checksum subtle-encoding 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cbc5188a16f729680b6d495b0deaa776944b8e509d24b7f989489b0d8bbcb63b"
"checksum syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" "checksum syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238"
"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" "checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
"checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" "checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e"
+1
View File
@@ -35,6 +35,7 @@ rsa = "0.1.4"
secrecy = "0.5" secrecy = "0.5"
sha-1 = "0.8" sha-1 = "0.8"
subtle = "2" subtle = "2"
subtle-encoding = "0.5"
x509-parser = "0.6" x509-parser = "0.6"
zeroize = "1" zeroize = "1"
+2 -2
View File
@@ -9,8 +9,8 @@
)] )]
use gumdrop::Options; use gumdrop::Options;
use yubikey_cli::commands::YubikeyCli; use yubikey_cli::commands::YubiKeyCli;
fn main() { fn main() {
YubikeyCli::parse_args_default_or_exit().run(); YubiKeyCli::parse_args_default_or_exit().run();
} }
+50 -21
View File
@@ -1,9 +1,10 @@
//! Commands of the CLI application //! Commands of the CLI application
pub mod readers; pub mod readers;
pub mod status;
use self::readers::ReadersCmd; use self::{readers::ReadersCmd, status::StatusCmd};
use crate::status::{self, STDOUT}; use crate::terminal::{self, STDOUT};
use gumdrop::Options; use gumdrop::Options;
use std::{ use std::{
env, env,
@@ -11,36 +12,29 @@ use std::{
process::exit, process::exit,
}; };
use termcolor::{ColorChoice, ColorSpec, WriteColor}; use termcolor::{ColorChoice, ColorSpec, WriteColor};
use yubikey_piv::{Serial, YubiKey};
/// The `yubikey` CLI utility /// The `yubikey` CLI utility
#[derive(Debug, Options)] #[derive(Debug, Options)]
pub struct YubikeyCli { pub struct YubiKeyCli {
/// Obtain help about the current command /// Obtain help about the current command
#[options(short = "h", help = "print help message")] #[options(short = "h", help = "print help message")]
pub help: bool, pub help: bool,
/// Specify the serial number of the YubiKey to connect to
#[options(
short = "s",
long = "serial",
help = "serial number of the YubiKey to connect to"
)]
pub serial: Option<Serial>,
/// Subcommand to execute. /// Subcommand to execute.
#[options(command)] #[options(command)]
pub command: Option<Commands>, pub command: Option<Commands>,
} }
impl YubikeyCli { impl YubiKeyCli {
/// Run the underlying command type or print usage info and exit
pub fn run(&self) {
// TODO(tarcieri): make this more configurable
status::set_color_choice(ColorChoice::Auto);
// Only show logs if `RUST_LOG` is set
if env::var("RUST_LOG").is_ok() {
env_logger::builder().format_timestamp(None).init();
}
match &self.command {
Some(cmd) => cmd.run(),
None => Self::print_usage().unwrap(),
}
}
/// Print usage information /// Print usage information
pub fn print_usage() -> Result<(), io::Error> { pub fn print_usage() -> Result<(), io::Error> {
let mut stdout = STDOUT.lock(); let mut stdout = STDOUT.lock();
@@ -65,6 +59,36 @@ impl YubikeyCli {
Ok(()) Ok(())
} }
/// Run the underlying command type or print usage info and exit
pub fn run(&self) {
// TODO(tarcieri): make this more configurable
terminal::set_color_choice(ColorChoice::Auto);
// Only show logs if `RUST_LOG` is set
if env::var("RUST_LOG").is_ok() {
env_logger::builder().format_timestamp(None).init();
}
match &self.command {
Some(cmd) => cmd.run(self.yubikey_init()),
None => Self::print_usage().unwrap(),
}
}
/// Initialize the YubiKey client driver
fn yubikey_init(&self) -> YubiKey {
match self.serial {
Some(serial) => YubiKey::open_by_serial(serial).unwrap_or_else(|e| {
status_err!("couldn't open YubiKey (serial #{}): {}", serial, e);
exit(1);
}),
None => YubiKey::open().unwrap_or_else(|e| {
status_err!("couldn't open default YubiKey: {}", e);
exit(1);
}),
}
}
} }
/// Subcommands of this application /// Subcommands of this application
@@ -81,15 +105,20 @@ pub enum Commands {
/// `readers` subcommand /// `readers` subcommand
#[options(help = "list detected readers")] #[options(help = "list detected readers")]
Readers(ReadersCmd), Readers(ReadersCmd),
/// `status` subcommand
#[options(help = "show yubikey status")]
Status(StatusCmd),
} }
impl Commands { impl Commands {
/// Run the given command /// Run the given command
pub fn run(&self) { pub fn run(&self, yubikey: YubiKey) {
match self { match self {
Commands::Help(help) => help.run(), Commands::Help(help) => help.run(),
Commands::Version(version) => version.run(), Commands::Version(version) => version.run(),
Commands::Readers(list) => list.run(), Commands::Readers(list) => list.run(),
Commands::Status(status) => status.run(yubikey),
} }
} }
} }
+27 -3
View File
@@ -1,8 +1,13 @@
//! List detected readers //! List detected readers
use crate::terminal::STDOUT;
use gumdrop::Options; use gumdrop::Options;
use std::process::exit; use std::{
use yubikey_piv::readers::Readers; io::{self, Write},
process::exit,
};
use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
use yubikey_piv::{Readers, Serial};
/// The `readers` subcommand /// The `readers` subcommand
#[derive(Debug, Options)] #[derive(Debug, Options)]
@@ -26,6 +31,9 @@ impl ReadersCmd {
exit(1); exit(1);
} }
let mut s = STDOUT.lock();
s.reset().unwrap();
for (i, reader) in readers_iter.enumerate() { for (i, reader) in readers_iter.enumerate() {
let name = reader.name(); let name = reader.name();
let mut yubikey = match reader.open() { let mut yubikey = match reader.open() {
@@ -34,7 +42,23 @@ impl ReadersCmd {
}; };
let serial = yubikey.serial(); let serial = yubikey.serial();
println!("{}: {} (serial: {})", i + 1, name, serial); self.print_reader(&mut s, i + 1, &name, serial).unwrap();
} }
} }
/// Print a reader
fn print_reader(
&self,
stream: &mut StandardStreamLock<'_>,
index: usize,
name: &str,
serial: Serial,
) -> Result<(), io::Error> {
stream.set_color(ColorSpec::new().set_bold(true))?;
write!(stream, "{:>3}:", index)?;
stream.reset()?;
writeln!(stream, " {} (serial: {})", name, serial)?;
stream.flush()?;
Ok(())
}
} }
+55
View File
@@ -0,0 +1,55 @@
//! Print device status
use crate::terminal::STDOUT;
use gumdrop::Options;
use std::io::{self, Write};
use termcolor::{ColorSpec, StandardStreamLock, WriteColor};
use yubikey_piv::YubiKey;
// String to use for `None`
const NONE_STR: &str = "<none>";
/// The `status` subcommand
#[derive(Debug, Options)]
pub struct StatusCmd {}
impl StatusCmd {
/// Run the `status` subcommand
pub fn run(&self, mut yk: YubiKey) {
let mut s = STDOUT.lock();
s.reset().unwrap();
self.attr(&mut s, "version", yk.version()).unwrap();
self.attr(&mut s, "serial", yk.serial()).unwrap();
if let Ok(chuid) = yk.chuid() {
self.attr(&mut s, "CHUID", chuid).unwrap();
} else {
self.attr(&mut s, "CHUID", NONE_STR).unwrap();
}
if let Ok(chuid) = yk.cccid() {
self.attr(&mut s, "CCC", chuid).unwrap();
} else {
self.attr(&mut s, "CCC", NONE_STR).unwrap();
}
self.attr(&mut s, "PIN retries", yk.get_pin_retries().unwrap())
.unwrap();
}
/// Print a status attribute
fn attr(
&self,
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(())
}
}
+1 -1
View File
@@ -9,6 +9,6 @@
)] )]
#[macro_use] #[macro_use]
pub mod status; pub mod terminal;
pub mod commands; pub mod commands;
+3 -3
View File
@@ -9,7 +9,7 @@ use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
#[macro_export] #[macro_export]
macro_rules! status_ok { macro_rules! status_ok {
($status:expr, $msg:expr) => { ($status:expr, $msg:expr) => {
$crate::status::Status::new() $crate::terminal::Status::new()
.justified() .justified()
.bold() .bold()
.color(termcolor::Color::Green) .color(termcolor::Color::Green)
@@ -25,7 +25,7 @@ macro_rules! status_ok {
#[macro_export] #[macro_export]
macro_rules! status_warn { macro_rules! status_warn {
($msg:expr) => { ($msg:expr) => {
$crate::status::Status::new() $crate::terminal::Status::new()
.bold() .bold()
.color(termcolor::Color::Yellow) .color(termcolor::Color::Yellow)
.status("warning:") .status("warning:")
@@ -40,7 +40,7 @@ macro_rules! status_warn {
#[macro_export] #[macro_export]
macro_rules! status_err { macro_rules! status_err {
($msg:expr) => { ($msg:expr) => {
$crate::status::Status::new() $crate::terminal::Status::new()
.bold() .bold()
.color(termcolor::Color::Red) .color(termcolor::Color::Red)
.status("error:") .status("error:")
+12 -1
View File
@@ -32,7 +32,8 @@
use crate::{error::Error, yubikey::YubiKey}; use crate::{error::Error, yubikey::YubiKey};
use getrandom::getrandom; use getrandom::getrandom;
use std::fmt::{self, Debug}; use std::fmt::{self, Debug, Display};
use subtle_encoding::hex;
/// CCCID size /// CCCID size
pub const CCCID_SIZE: usize = 14; pub const CCCID_SIZE: usize = 14;
@@ -116,3 +117,13 @@ impl Debug for CCC {
write!(f, "CCC({:?})", &self.0[..]) write!(f, "CCC({:?})", &self.0[..])
} }
} }
impl Display for CCC {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
String::from_utf8(hex::encode(&self.0[..])).unwrap()
)
}
}
+12 -1
View File
@@ -32,7 +32,8 @@
use crate::{error::Error, yubikey::YubiKey}; use crate::{error::Error, yubikey::YubiKey};
use getrandom::getrandom; use getrandom::getrandom;
use std::fmt::{self, Debug}; use std::fmt::{self, Debug, Display};
use subtle_encoding::hex;
/// CHUID size /// CHUID size
pub const CHUID_SIZE: usize = 59; pub const CHUID_SIZE: usize = 59;
@@ -149,6 +150,16 @@ impl CHUID {
} }
} }
impl Display for CHUID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
String::from_utf8(hex::encode(&self.0[..])).unwrap()
)
}
}
impl Debug for CHUID { impl Debug for CHUID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CHUID({:?})", &self.0[..]) write!(f, "CHUID({:?})", &self.0[..])
+7 -1
View File
@@ -156,7 +156,13 @@ pub mod settings;
mod transaction; mod transaction;
pub mod yubikey; pub mod yubikey;
pub use self::{error::Error, key::Key, mgm::MgmKey, readers::Readers, yubikey::YubiKey}; pub use self::{
error::Error,
key::Key,
mgm::MgmKey,
readers::Readers,
yubikey::{Serial, YubiKey},
};
/// Object identifiers /// Object identifiers
pub type ObjectId = u32; pub type ObjectId = u32;
+9 -1
View File
@@ -43,6 +43,7 @@ use pcsc::Card;
use std::{ use std::{
convert::TryFrom, convert::TryFrom,
fmt::{self, Display}, fmt::{self, Display},
str::FromStr,
}; };
#[cfg(feature = "untested")] #[cfg(feature = "untested")]
@@ -103,6 +104,14 @@ impl From<Serial> for u32 {
} }
} }
impl FromStr for Serial {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
u32::from_str(s).map(Serial).map_err(|_| Error::ParseError)
}
}
impl Display for Serial { impl Display for Serial {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0) write!(f, "{}", self.0)
@@ -350,7 +359,6 @@ impl YubiKey {
} }
/// Get the number of PIN retries /// Get the number of PIN retries
#[cfg(feature = "untested")]
pub fn get_pin_retries(&mut self) -> Result<u8, Error> { pub fn get_pin_retries(&mut self) -> Result<u8, Error> {
let txn = self.begin_transaction()?; let txn = self.begin_transaction()?;