// args.rs
// *************************************************************************
// * Copyright (C) 2018 Daniel Mueller (deso@posteo.net) *
// * *
// * This program is free software: you can redistribute it and/or modify *
// * it under the terms of the GNU General Public License as published by *
// * the Free Software Foundation, either version 3 of the License, or *
// * (at your option) any later version. *
// * *
// * This program is distributed in the hope that it will be useful, *
// * but WITHOUT ANY WARRANTY; without even the implied warranty of *
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
// * GNU General Public License for more details. *
// * *
// * You should have received a copy of the GNU General Public License *
// * along with this program. If not, see . *
// *************************************************************************
use std::fmt;
use std::io;
use std::result;
use std::str;
use crate::commands;
use crate::error::Error;
type Result = result::Result;
/// A top-level command for nitrocli.
#[derive(Debug)]
pub enum Command {
Clear,
Close,
Config,
Open,
Otp,
Status,
}
impl Command {
/// Execute this command with the given arguments.
pub fn execute(&self, args: Vec) -> Result<()> {
match *self {
Command::Clear => clear(args),
Command::Close => close(args),
Command::Config => config(args),
Command::Open => open(args),
Command::Otp => otp(args),
Command::Status => status(args),
}
}
}
impl fmt::Display for Command {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match *self {
Command::Clear => "clear",
Command::Close => "close",
Command::Config => "config",
Command::Open => "open",
Command::Otp => "otp",
Command::Status => "status",
}
)
}
}
impl str::FromStr for Command {
type Err = ();
fn from_str(s: &str) -> result::Result {
match s {
"clear" => Ok(Command::Clear),
"close" => Ok(Command::Close),
"config" => Ok(Command::Config),
"open" => Ok(Command::Open),
"otp" => Ok(Command::Otp),
"status" => Ok(Command::Status),
_ => Err(()),
}
}
}
#[derive(Debug)]
enum ConfigCommand {
Get,
}
impl ConfigCommand {
fn execute(&self, args: Vec) -> Result<()> {
match *self {
ConfigCommand::Get => config_get(args),
}
}
}
impl fmt::Display for ConfigCommand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match *self {
ConfigCommand::Get => "get",
}
)
}
}
impl str::FromStr for ConfigCommand {
type Err = ();
fn from_str(s: &str) -> result::Result {
match s {
"get" => Ok(ConfigCommand::Get),
_ => Err(()),
}
}
}
#[derive(Debug)]
enum OtpCommand {
Clear,
Get,
Set,
Status,
}
impl OtpCommand {
fn execute(&self, args: Vec) -> Result<()> {
match *self {
OtpCommand::Clear => otp_clear(args),
OtpCommand::Get => otp_get(args),
OtpCommand::Set => otp_set(args),
OtpCommand::Status => otp_status(args),
}
}
}
impl fmt::Display for OtpCommand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match *self {
OtpCommand::Clear => "clear",
OtpCommand::Get => "get",
OtpCommand::Set => "set",
OtpCommand::Status => "status",
}
)
}
}
impl str::FromStr for OtpCommand {
type Err = ();
fn from_str(s: &str) -> result::Result {
match s {
"clear" => Ok(OtpCommand::Clear),
"get" => Ok(OtpCommand::Get),
"set" => Ok(OtpCommand::Set),
"status" => Ok(OtpCommand::Status),
_ => Err(()),
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum OtpAlgorithm {
Hotp,
Totp,
}
impl fmt::Display for OtpAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match *self {
OtpAlgorithm::Hotp => "hotp",
OtpAlgorithm::Totp => "totp",
}
)
}
}
impl str::FromStr for OtpAlgorithm {
type Err = ();
fn from_str(s: &str) -> result::Result {
match s {
"hotp" => Ok(OtpAlgorithm::Hotp),
"totp" => Ok(OtpAlgorithm::Totp),
_ => Err(()),
}
}
}
#[derive(Clone, Copy, Debug)]
enum OtpMode {
SixDigits,
EightDigits,
}
impl fmt::Display for OtpMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match *self {
OtpMode::SixDigits => "6",
OtpMode::EightDigits => "8",
}
)
}
}
impl str::FromStr for OtpMode {
type Err = ();
fn from_str(s: &str) -> result::Result {
match s {
"6" => Ok(OtpMode::SixDigits),
"8" => Ok(OtpMode::EightDigits),
_ => Err(()),
}
}
}
impl From for nitrokey::OtpMode {
fn from(mode: OtpMode) -> Self {
match mode {
OtpMode::SixDigits => nitrokey::OtpMode::SixDigits,
OtpMode::EightDigits => nitrokey::OtpMode::EightDigits,
}
}
}
fn parse(parser: &argparse::ArgumentParser<'_>, args: Vec) -> Result<()> {
if let Err(err) = parser.parse(args, &mut io::stdout(), &mut io::stderr()) {
Err(Error::ArgparseError(err))
} else {
Ok(())
}
}
/// Inquire the status of the nitrokey.
fn status(args: Vec) -> Result<()> {
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Print the status of the connected Nitrokey device");
parse(&parser, args)?;
commands::status()
}
/// Open the encrypted volume on the nitrokey.
fn open(args: Vec) -> Result<()> {
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Opens the encrypted volume on a Nitrokey Storage");
parse(&parser, args)?;
commands::open()
}
/// Close the previously opened encrypted volume.
fn close(args: Vec) -> Result<()> {
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Closes the encrypted volume on a Nitrokey Storage");
parse(&parser, args)?;
commands::close()
}
/// Clear the PIN stored when opening the nitrokey's encrypted volume.
fn clear(args: Vec) -> Result<()> {
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Clears the cached passphrases");
parse(&parser, args)?;
commands::clear()
}
/// Execute a config subcommand.
fn config(args: Vec) -> Result<()> {
let mut subcommand = ConfigCommand::Get;
let mut subargs = vec![];
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Reads or writes the device configuration");
let _ = parser.refer(&mut subcommand).required().add_argument(
"subcommand",
argparse::Store,
"The subcommand to execute (get|set)",
);
let _ = parser.refer(&mut subargs).add_argument(
"arguments",
argparse::List,
"The arguments for the subcommand",
);
parser.stop_on_first_argument(true);
parse(&parser, args)?;
drop(parser);
subargs.insert(0, format!("nitrocli config {}", subcommand));
subcommand.execute(subargs)
}
/// Read the Nitrokey configuration.
fn config_get(args: Vec) -> Result<()> {
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Prints the Nitrokey configuration");
parse(&parser, args)?;
commands::config_get()
}
/// Execute an OTP subcommand.
fn otp(args: Vec) -> Result<()> {
let mut subcommand = OtpCommand::Get;
let mut subargs = vec![];
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Accesses one-time passwords");
let _ = parser.refer(&mut subcommand).required().add_argument(
"subcommand",
argparse::Store,
"The subcommand to execute (clear|get|set|status)",
);
let _ = parser.refer(&mut subargs).add_argument(
"arguments",
argparse::List,
"The arguments for the subcommand",
);
parser.stop_on_first_argument(true);
parse(&parser, args)?;
drop(parser);
subargs.insert(0, format!("nitrocli otp {}", subcommand));
subcommand.execute(subargs)
}
/// Generate a one-time password on the Nitrokey device.
fn otp_get(args: Vec) -> Result<()> {
let mut slot: u8 = 0;
let mut algorithm = OtpAlgorithm::Totp;
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Generates a one-time password");
let _ =
parser
.refer(&mut slot)
.required()
.add_argument("slot", argparse::Store, "The OTP slot to use");
let _ = parser.refer(&mut algorithm).add_option(
&["-a", "--algorithm"],
argparse::Store,
"The OTP algorithm to use (hotp|totp)",
);
parse(&parser, args)?;
drop(parser);
commands::otp_get(slot, algorithm)
}
/// Configure a one-time password slot on the Nitrokey device.
pub fn otp_set(args: Vec) -> Result<()> {
let mut slot: u8 = 0;
let mut algorithm = OtpAlgorithm::Totp;
let mut name = "".to_owned();
let mut secret = "".to_owned();
let mut digits = OtpMode::SixDigits;
let mut counter: u64 = 0;
let mut time_window: u16 = 30;
let mut ascii = false;
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Configures a one-time password slot");
let _ =
parser
.refer(&mut slot)
.required()
.add_argument("slot", argparse::Store, "The OTP slot to use");
let _ = parser.refer(&mut algorithm).add_option(
&["-a", "--algorithm"],
argparse::Store,
"The OTP algorithm to use (hotp or totp, default: totp",
);
let _ = parser.refer(&mut name).required().add_argument(
"name",
argparse::Store,
"The name of the slot",
);
let _ = parser.refer(&mut secret).required().add_argument(
"secret",
argparse::Store,
"The secret to store on the slot as a hexadecimal string (unless --ascii is set)",
);
let _ = parser.refer(&mut digits).add_option(
&["-d", "--digits"],
argparse::Store,
"The number of digits to use for the one-time password (6 or 8, default: 6)",
);
let _ = parser.refer(&mut counter).add_option(
&["-c", "--counter"],
argparse::Store,
"The counter value for HOTP (default: 0)",
);
let _ = parser.refer(&mut time_window).add_option(
&["-t", "--time-window"],
argparse::Store,
"The time window for TOTP (default: 30)",
);
let _ = parser.refer(&mut ascii).add_option(
&["--ascii"],
argparse::StoreTrue,
"Interpret the given secret as an ASCII string of the secret",
);
parse(&parser, args)?;
drop(parser);
let data = nitrokey::OtpSlotData {
number: slot,
name,
secret,
mode: nitrokey::OtpMode::from(digits),
use_enter: false,
token_id: None,
};
commands::otp_set(data, algorithm, counter, time_window, ascii)
}
/// Clear an OTP slot.
fn otp_clear(args: Vec) -> Result<()> {
let mut slot: u8 = 0;
let mut algorithm = OtpAlgorithm::Totp;
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Clears a one-time password slot");
let _ = parser.refer(&mut slot).required().add_argument(
"slot",
argparse::Store,
"The OTP slot to clear",
);
let _ = parser.refer(&mut algorithm).add_option(
&["-a", "--algorithm"],
argparse::Store,
"The OTP algorithm to use (hotp|totp)",
);
parse(&parser, args)?;
drop(parser);
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)> {
let mut command = Command::Status;
let mut subargs = vec![];
let mut parser = argparse::ArgumentParser::new();
parser.set_description("Provides access to a Nitrokey device");
let _ = parser.refer(&mut command).required().add_argument(
"command",
argparse::Store,
"The command to execute (clear|close|config|open|otp|status)",
);
let _ = parser.refer(&mut subargs).add_argument(
"arguments",
argparse::List,
"The arguments for the command",
);
parser.stop_on_first_argument(true);
parse(&parser, args)?;
drop(parser);
subargs.insert(0, format!("nitrocli {}", command));
Ok((command, subargs))
}
/// Parse the command-line arguments and execute the selected command.
pub fn handle_arguments(args: Vec) -> Result<()> {
let (command, args) = parse_arguments(args)?;
command.execute(args)
}