From 96adac9c5d3399a4890f54515a92472067771303 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 25 Dec 2018 10:41:06 +0100 Subject: Implement the config set subcommand This change implements the config set subcommand. The subcommand changes the configuration of a Nitrokey device. Its structure is more complex as it allows partial modifications: The user does not have to change all settings, but may choose to change only some. At the same time, the binding settings can be either set to a value or disabled. Therefore, we have the --{num,caps,scrol}lock options to set a value and the --no-{num,caps,scrol}lock options to disable the value. If none of the two is set, the setting is not changed. --- nitrocli/src/args.rs | 107 +++++++++++++++++++++++++++++++++++++++++++++++ nitrocli/src/commands.rs | 24 ++++++++++- 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/nitrocli/src/args.rs b/nitrocli/src/args.rs index 298b157..36da560 100644 --- a/nitrocli/src/args.rs +++ b/nitrocli/src/args.rs @@ -88,12 +88,14 @@ impl str::FromStr for Command { #[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), } } } @@ -105,6 +107,7 @@ impl fmt::Display for ConfigCommand { "{}", match *self { ConfigCommand::Get => "get", + ConfigCommand::Set => "set", } ) } @@ -116,11 +119,47 @@ impl str::FromStr for ConfigCommand { 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, @@ -317,6 +356,74 @@ fn config_get(args: Vec) -> Result<()> { 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; diff --git a/nitrocli/src/commands.rs b/nitrocli/src/commands.rs index 8ce1076..9aef2de 100644 --- a/nitrocli/src/commands.rs +++ b/nitrocli/src/commands.rs @@ -128,7 +128,7 @@ where { let mut data = data; let mut retry = 3; - let mut error_msg: Option<&str> = None; + let mut error_msg = None; loop { let passphrase = match pinentry::inquire_passphrase(pin, error_msg) { Ok(passphrase) => passphrase, @@ -304,6 +304,28 @@ pub fn config_get() -> Result<()> { Ok(()) } +/// Write the Nitrokey configuration. +pub fn config_set( + numlock: args::ConfigOption, + capslock: args::ConfigOption, + scrollock: args::ConfigOption, + user_password: Option, +) -> Result<()> { + let device = authenticate_admin(get_device()?)?; + let config = device + .get_config() + .map_err(|err| get_error("Could not get configuration", &err))?; + let config = nitrokey::Config { + numlock: numlock.or(config.numlock), + capslock: capslock.or(config.capslock), + scrollock: scrollock.or(config.scrollock), + user_password: user_password.unwrap_or(config.user_password), + }; + device + .write_config(config) + .map_err(|err| get_error("Could not set configuration", &err)) +} + fn get_otp(slot: u8, algorithm: args::OtpAlgorithm, device: &T) -> Result { match algorithm { args::OtpAlgorithm::Hotp => device.get_hotp_code(slot), -- cgit v1.2.1