// 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; use crate::pinentry; type Result = result::Result; /// A top-level command for nitrocli. #[derive(Debug)] pub enum Command { Config, Otp, Pin, Pws, Status, Storage, } impl Command { /// Execute this command with the given arguments. pub fn execute(&self, args: Vec) -> Result<()> { match *self { Command::Config => config(args), Command::Otp => otp(args), Command::Pin => pin(args), Command::Pws => pws(args), Command::Status => status(args), Command::Storage => storage(args), } } } impl fmt::Display for Command { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}", match *self { Command::Config => "config", Command::Otp => "otp", Command::Pin => "pin", Command::Pws => "pws", Command::Status => "status", Command::Storage => "storage", } ) } } impl str::FromStr for Command { type Err = (); fn from_str(s: &str) -> result::Result { match s { "config" => Ok(Command::Config), "otp" => Ok(Command::Otp), "pin" => Ok(Command::Pin), "pws" => Ok(Command::Pws), "status" => Ok(Command::Status), "storage" => Ok(Command::Storage), _ => Err(()), } } } #[derive(Debug)] enum ConfigCommand { Get, Set, } impl ConfigCommand { fn execute(&self, args: Vec) -> Result<()> { match *self { ConfigCommand::Get => config_get(args), ConfigCommand::Set => config_set(args), } } } impl fmt::Display for ConfigCommand { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}", match *self { ConfigCommand::Get => "get", ConfigCommand::Set => "set", } ) } } impl str::FromStr for ConfigCommand { type Err = (); fn from_str(s: &str) -> result::Result { match s { "get" => Ok(ConfigCommand::Get), "set" => Ok(ConfigCommand::Set), _ => Err(()), } } } #[derive(Clone, Copy, Debug)] pub enum ConfigOption { Enable(T), Disable, Ignore, } impl ConfigOption { fn try_from(disable: bool, value: Option, name: &'static str) -> Result { if disable { if value.is_some() { Err(Error::Error(format!( "--{name} and --no-{name} are mutually exclusive", name = name ))) } else { Ok(ConfigOption::Disable) } } else { match value { Some(value) => Ok(ConfigOption::Enable(value)), None => Ok(ConfigOption::Ignore), } } } pub fn or(self, default: Option) -> Option { match self { ConfigOption::Enable(value) => Some(value), ConfigOption::Disable => None, ConfigOption::Ignore => default, } } } #[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, PartialEq)] 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, } } } #[derive(Debug)] enum PinCommand { Clear, Set, Unblock, } impl PinCommand { fn execute(&self, args: Vec) -> Result<()> { match *self { PinCommand::Clear => pin_clear(args), PinCommand::Set => pin_set(args), PinCommand::Unblock => pin_unblock(args), } } } impl fmt::Display for PinCommand { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}", match *self { PinCommand::Clear => "clear", PinCommand::Set => "set", PinCommand::Unblock => "unblock", } ) } } impl str::FromStr for PinCommand { type Err = (); fn from_str(s: &str) -> result::Result { match s { "clear" => Ok(PinCommand::Clear), "set" => Ok(PinCommand::Set), "unblock" => Ok(PinCommand::Unblock), _ => Err(()), } } } #[derive(Debug)] enum PwsCommand { Clear, Get, Set, } impl PwsCommand { fn execute(&self, args: Vec) -> Result<()> { match *self { PwsCommand::Clear => pws_clear(args), PwsCommand::Get => pws_get(args), PwsCommand::Set => pws_set(args), } } } impl fmt::Display for PwsCommand { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}", match *self { PwsCommand::Clear => "clear", PwsCommand::Get => "get", PwsCommand::Set => "set", } ) } } impl str::FromStr for PwsCommand { type Err = (); fn from_str(s: &str) -> result::Result { match s { "clear" => Ok(PwsCommand::Clear), "get" => Ok(PwsCommand::Get), "set" => Ok(PwsCommand::Set), _ => Err(()), } } } 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("Prints the status of the connected Nitrokey device"); parse(&parser, args)?; commands::status() } #[derive(Debug)] enum StorageCommand { Close, Open, Status, } impl StorageCommand { fn execute(&self, args: Vec) -> Result<()> { match *self { StorageCommand::Close => storage_close(args), StorageCommand::Open => storage_open(args), StorageCommand::Status => storage_status(args), } } } impl fmt::Display for StorageCommand { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}", match *self { StorageCommand::Close => "close", StorageCommand::Open => "open", StorageCommand::Status => "status", } ) } } impl str::FromStr for StorageCommand { type Err = (); fn from_str(s: &str) -> result::Result { match s { "close" => Ok(StorageCommand::Close), "open" => Ok(StorageCommand::Open), "status" => Ok(StorageCommand::Status), _ => Err(()), } } } /// Execute a storage subcommand. fn storage(args: Vec) -> Result<()> { let mut subcommand = StorageCommand::Open; let mut subargs = vec![]; let mut parser = argparse::ArgumentParser::new(); parser.set_description("Interacts with the device's storage"); let _ = parser.refer(&mut subcommand).required().add_argument( "subcommand", argparse::Store, "The subcommand to execute (open|close)", ); 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 storage {}", subcommand)); subcommand.execute(subargs) } /// Open the encrypted volume on the nitrokey. fn storage_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::storage_open() } /// Close the previously opened encrypted volume. fn storage_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::storage_close() } /// Print the status of the nitrokey's storage. fn storage_status(args: Vec) -> Result<()> { let mut parser = argparse::ArgumentParser::new(); parser.set_description("Prints the status of the Nitrokey's storage"); parse(&parser, args)?; commands::storage_status() } /// 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() } /// Write the Nitrokey configuration. fn config_set(args: Vec) -> Result<()> { let mut numlock = None; let mut no_numlock = false; let mut capslock = None; let mut no_capslock = false; let mut scrollock = None; let mut no_scrollock = false; let mut otp_pin = false; let mut no_otp_pin = false; let mut parser = argparse::ArgumentParser::new(); parser.set_description("Changes the Nitrokey configuration"); let _ = parser.refer(&mut numlock).add_option( &["-n", "--numlock"], argparse::StoreOption, "Set the numlock option to the given HOTP slot", ); let _ = parser.refer(&mut no_numlock).add_option( &["-N", "--no-numlock"], argparse::StoreTrue, "Unset the numlock option", ); let _ = parser.refer(&mut capslock).add_option( &["-c", "--capslock"], argparse::StoreOption, "Set the capslock option to the given HOTP slot", ); let _ = parser.refer(&mut no_capslock).add_option( &["-C", "--no-capslock"], argparse::StoreTrue, "Unset the capslock option", ); let _ = parser.refer(&mut scrollock).add_option( &["-s", "--scrollock"], argparse::StoreOption, "Set the scrollock option to the given HOTP slot", ); let _ = parser.refer(&mut no_scrollock).add_option( &["-S", "--no-scrollock"], argparse::StoreTrue, "Unset the scrollock option", ); let _ = parser.refer(&mut otp_pin).add_option( &["-o", "--otp-pin"], argparse::StoreTrue, "Require the user PIN to generate one-time passwords", ); let _ = parser.refer(&mut no_otp_pin).add_option( &["-O", "--no-otp-pin"], argparse::StoreTrue, "Allow one-time password generation without PIN", ); parse(&parser, args)?; drop(parser); let numlock = ConfigOption::try_from(no_numlock, numlock, "numlock")?; let capslock = ConfigOption::try_from(no_capslock, capslock, "capslock")?; let scrollock = ConfigOption::try_from(no_scrollock, scrollock, "scrollock")?; let otp_pin = if otp_pin { Some(true) } else if no_otp_pin { Some(false) } else { None }; commands::config_set(numlock, capslock, scrollock, otp_pin) } /// 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 time: Option = None; 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)", ); let _ = parser.refer(&mut time).add_option( &["-t", "--time"], argparse::StoreOption, "The time to use for TOTP generation (Unix timestamp, default: system time)", ); parse(&parser, args)?; drop(parser); commands::otp_get(slot, algorithm, time) } /// 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) } /// Execute a PIN subcommand. fn pin(args: Vec) -> Result<()> { let mut subcommand = PinCommand::Clear; let mut subargs = vec![]; let mut parser = argparse::ArgumentParser::new(); parser.set_description("Manages the Nitrokey PINs"); let _ = parser.refer(&mut subcommand).required().add_argument( "subcommand", argparse::Store, "The subcommand to execute (clear|set|unblock)", ); 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 pin {}", subcommand)); subcommand.execute(subargs) } /// Clear the PIN as cached by various other commands. fn pin_clear(args: Vec) -> Result<()> { let mut parser = argparse::ArgumentParser::new(); parser.set_description("Clears the cached PINs"); parse(&parser, args)?; commands::pin_clear() } /// Change a PIN. fn pin_set(args: Vec) -> Result<()> { let mut pintype = pinentry::PinType::User; let mut parser = argparse::ArgumentParser::new(); parser.set_description("Changes a PIN"); let _ = parser.refer(&mut pintype).required().add_argument( "type", argparse::Store, "The PIN type to change (admin|user)", ); parse(&parser, args)?; drop(parser); commands::pin_set(pintype) } /// Unblock and reset the user PIN. fn pin_unblock(args: Vec) -> Result<()> { let mut parser = argparse::ArgumentParser::new(); parser.set_description("Unblocks and resets the user PIN"); parse(&parser, args)?; commands::pin_unblock() } /// Execute a PWS subcommand. fn pws(args: Vec) -> Result<()> { let mut subcommand = PwsCommand::Get; let mut subargs = vec![]; let mut parser = argparse::ArgumentParser::new(); parser.set_description("Accesses the password safe"); let _ = parser.refer(&mut subcommand).required().add_argument( "subcommand", argparse::Store, "The subcommand to execute (clear|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 pws {}", subcommand)); subcommand.execute(subargs) } /// Access a slot of the password safe on the Nitrokey. fn pws_get(args: Vec) -> Result<()> { let mut slot: u8 = 0; let mut name = false; let mut login = false; let mut password = false; let mut quiet = false; let mut parser = argparse::ArgumentParser::new(); parser.set_description("Reads a password safe slot"); let _ = parser.refer(&mut slot).required().add_argument( "slot", argparse::Store, "The PWS slot to read", ); let _ = parser.refer(&mut name).add_option( &["-n", "--name"], argparse::StoreTrue, "Show the name stored on the slot", ); let _ = parser.refer(&mut login).add_option( &["-l", "--login"], argparse::StoreTrue, "Show the login stored on the slot", ); let _ = parser.refer(&mut password).add_option( &["-p", "--password"], argparse::StoreTrue, "Show the password stored on the slot", ); let _ = parser.refer(&mut quiet).add_option( &["-q", "--quiet"], argparse::StoreTrue, "Print the stored data without description", ); parse(&parser, args)?; drop(parser); commands::pws_get(slot, name, login, password, quiet) } /// Set a slot of the password safe on the Nitrokey. fn pws_set(args: Vec) -> Result<()> { let mut slot: u8 = 0; let mut name = String::new(); let mut login = String::new(); let mut password = String::new(); let mut parser = argparse::ArgumentParser::new(); parser.set_description("Writes a password safe slot"); let _ = parser.refer(&mut slot).required().add_argument( "slot", argparse::Store, "The PWS slot to read", ); let _ = parser.refer(&mut name).required().add_argument( "name", argparse::Store, "The name to store on the slot", ); let _ = parser.refer(&mut login).required().add_argument( "login", argparse::Store, "The login to store on the slot", ); let _ = parser.refer(&mut password).required().add_argument( "password", argparse::Store, "The password to store on the slot", ); parse(&parser, args)?; drop(parser); commands::pws_set(slot, &name, &login, &password) } /// Clear a PWS slot. fn pws_clear(args: Vec) -> Result<()> { let mut slot: u8 = 0; let mut parser = argparse::ArgumentParser::new(); parser.set_description("Clears a password safe slot"); let _ = parser.refer(&mut slot).required().add_argument( "slot", argparse::Store, "The PWS slot to clear", ); parse(&parser, args)?; drop(parser); commands::pws_clear(slot) } /// 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 (config|otp|pin|pws|status|storage)", ); 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) }