From ac52b71b0c23c415eede3b8fef50831ae2fe01bc Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 23 Dec 2018 02:21:21 +0100 Subject: Implement the otp status subcommand This patch introduces the `otp status` subcommand that lists all OTP slots and their current status. To avoid hardcoding the number of slots per type, we iterate all slots until we get an `InvalidSlot` error (assuming that the set of valid slots is {0, ..., n} for some n). The `status` command is quite slow as we have to query each slot separately. --- nitrocli/src/args.rs | 22 +++++++++++++++++++++- nitrocli/src/commands.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/nitrocli/src/args.rs b/nitrocli/src/args.rs index 5c2cbb8..ab80f3d 100644 --- a/nitrocli/src/args.rs +++ b/nitrocli/src/args.rs @@ -86,6 +86,7 @@ enum OtpCommand { Clear, Get, Set, + Status, } impl OtpCommand { @@ -94,6 +95,7 @@ impl OtpCommand { OtpCommand::Clear => otp_clear(args), OtpCommand::Get => otp_get(args), OtpCommand::Set => otp_set(args), + OtpCommand::Status => otp_status(args), } } } @@ -107,6 +109,7 @@ impl fmt::Display for OtpCommand { OtpCommand::Clear => "clear", OtpCommand::Get => "get", OtpCommand::Set => "set", + OtpCommand::Status => "status", } ) } @@ -120,6 +123,7 @@ impl str::FromStr for OtpCommand { "clear" => Ok(OtpCommand::Clear), "get" => Ok(OtpCommand::Get), "set" => Ok(OtpCommand::Set), + "status" => Ok(OtpCommand::Status), _ => Err(()), } } @@ -249,7 +253,7 @@ fn otp(args: Vec) -> Result<()> { let _ = parser.refer(&mut subcommand).required().add_argument( "subcommand", argparse::Store, - "The subcommand to execute (clear|get|set)", + "The subcommand to execute (clear|get|set|status)", ); let _ = parser.refer(&mut subargs).add_argument( "arguments", @@ -374,6 +378,22 @@ fn otp_clear(args: Vec) -> Result<()> { commands::otp_clear(slot, algorithm) } +/// Print the status of the OTP slots. +fn otp_status(args: Vec) -> Result<()> { + let mut all = false; + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Prints the status of the OTP slots"); + let _ = parser.refer(&mut all).add_option( + &["-a", "--all"], + argparse::StoreTrue, + "Show slots that are not programmed", + ); + parse(&parser, args)?; + drop(parser); + + commands::otp_status(all) +} + /// Parse the command-line arguments and return the selected command and /// the remaining arguments for the command. fn parse_arguments(args: Vec) -> Result<(Command, Vec)> { diff --git a/nitrocli/src/commands.rs b/nitrocli/src/commands.rs index 93e9bd3..3546e2e 100644 --- a/nitrocli/src/commands.rs +++ b/nitrocli/src/commands.rs @@ -333,6 +333,50 @@ pub fn otp_clear(slot: u8, algorithm: args::OtpAlgorithm) -> Result<()> { Ok(()) } +fn print_otp_status( + algorithm: args::OtpAlgorithm, + device: &nitrokey::DeviceWrapper, + all: bool, +) -> Result<()> { + let mut slot: u8 = 0; + loop { + let result = match algorithm { + args::OtpAlgorithm::Hotp => device.get_hotp_slot_name(slot), + args::OtpAlgorithm::Totp => device.get_totp_slot_name(slot), + }; + slot = match slot.checked_add(1) { + Some(slot) => slot, + None => { + return Err(Error::Error( + "Integer overflow when iterating OTP slots".to_string(), + )) + } + }; + let name = match result { + Ok(name) => name, + Err(nitrokey::CommandError::InvalidSlot) => return Ok(()), + Err(nitrokey::CommandError::SlotNotProgrammed) => { + if all { + "[not programmed]".to_string() + } else { + continue; + } + } + Err(err) => return Err(get_error("Could not check OTP slot", &err)), + }; + println!("{}\t{}\t{}", algorithm, slot - 1, name); + } +} + +/// Print the status of the OTP slots. +pub fn otp_status(all: bool) -> Result<()> { + let device = get_device()?; + println!("alg\tslot\tname"); + print_otp_status(args::OtpAlgorithm::Hotp, &device, all)?; + print_otp_status(args::OtpAlgorithm::Totp, &device, all)?; + Ok(()) +} + #[cfg(test)] mod tests { use super::*; -- cgit v1.2.1