diff options
author | Daniel Mueller <deso@posteo.net> | 2020-04-01 22:59:22 -0700 |
---|---|---|
committer | Daniel Mueller <deso@posteo.net> | 2020-04-01 22:59:22 -0700 |
commit | a3f8ae474d153048c5a9252125099ef49cc6b5f4 (patch) | |
tree | 106663b08f026cd873f0b6168a1d108925ab8e24 | |
parent | 550a730cb7ab9c9e3963ba46ac7cad93535f13d5 (diff) | |
download | nitrocli-a3f8ae474d153048c5a9252125099ef49cc6b5f4.tar.gz nitrocli-a3f8ae474d153048c5a9252125099ef49cc6b5f4.tar.bz2 |
Factor out arg_defs.rs for argument parsing related types
This change marks the first step in a restructuring of the argument
handling code, the ultimate goal of which is a separation of the type
definitions as used by structopt from the logic associated with it. This
change in particular introduces a new module, arg_defs, that contains
all those type definitions that previously resided in the args module.
-rw-r--r-- | src/arg_defs.rs | 428 | ||||
-rw-r--r-- | src/arg_util.rs | 4 | ||||
-rw-r--r-- | src/args.rs | 422 | ||||
-rw-r--r-- | src/commands.rs | 55 | ||||
-rw-r--r-- | src/main.rs | 1 | ||||
-rw-r--r-- | src/tests/otp.rs | 6 |
6 files changed, 470 insertions, 446 deletions
diff --git a/src/arg_defs.rs b/src/arg_defs.rs new file mode 100644 index 0000000..d195841 --- /dev/null +++ b/src/arg_defs.rs @@ -0,0 +1,428 @@ +// arg_defs.rs + +// ************************************************************************* +// * Copyright (C) 2020 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 <http://www.gnu.org/licenses/>. * +// ************************************************************************* + +use crate::args; +use crate::commands; +use crate::error::Error; +use crate::pinentry; + +/// Provides access to a Nitrokey device +#[derive(structopt::StructOpt)] +#[structopt(name = "nitrocli")] +pub struct Args { + /// Increases the log level (can be supplied multiple times) + #[structopt(short, long, global = true, parse(from_occurrences))] + pub verbose: u8, + /// Selects the device model to connect to + #[structopt(short, long, global = true, possible_values = &DeviceModel::all_str())] + pub model: Option<DeviceModel>, + #[structopt(subcommand)] + pub cmd: Command, +} + +/// The available Nitrokey models. +#[allow(unused_doc_comments)] +Enum! {DeviceModel, [ + Pro => "pro", + Storage => "storage", +]} + +impl DeviceModel { + pub fn as_user_facing_str(&self) -> &str { + match self { + DeviceModel::Pro => "Pro", + DeviceModel::Storage => "Storage", + } + } +} + +impl From<DeviceModel> for nitrokey::Model { + fn from(model: DeviceModel) -> nitrokey::Model { + match model { + DeviceModel::Pro => nitrokey::Model::Pro, + DeviceModel::Storage => nitrokey::Model::Storage, + } + } +} + +/// A top-level command for nitrocli. +#[allow(unused_doc_comments)] +Command! {Command, [ + /// Reads or writes the device configuration + Config(ConfigArgs) => |ctx, args: ConfigArgs| args.subcmd.execute(ctx), + /// Interacts with the device's encrypted volume + Encrypted(EncryptedArgs) => |ctx, args: EncryptedArgs| args.subcmd.execute(ctx), + /// Interacts with the device's hidden volume + Hidden(HiddenArgs) => |ctx, args: HiddenArgs| args.subcmd.execute(ctx), + /// Lists the attached Nitrokey devices + List(ListArgs) => |ctx, args: ListArgs| commands::list(ctx, args.no_connect), + /// Locks the connected Nitrokey device + Lock => commands::lock, + /// Accesses one-time passwords + Otp(OtpArgs) => |ctx, args: OtpArgs| args.subcmd.execute(ctx), + /// Manages the Nitrokey PINs + Pin(PinArgs) => |ctx, args: PinArgs| args.subcmd.execute(ctx), + /// Accesses the password safe + Pws(PwsArgs) => |ctx, args: PwsArgs| args.subcmd.execute(ctx), + /// Performs a factory reset + Reset => commands::reset, + /// Prints the status of the connected Nitrokey device + Status => commands::status, + /// Interacts with the device's unencrypted volume + Unencrypted(UnencryptedArgs) => |ctx, args: UnencryptedArgs| args.subcmd.execute(ctx), +]} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct ConfigArgs { + #[structopt(subcommand)] + subcmd: ConfigCommand, +} + +Command! {ConfigCommand, [ + /// Prints the Nitrokey configuration + Get => commands::config_get, + /// Changes the Nitrokey configuration + Set(ConfigSetArgs) => args::config_set, +]} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct ConfigSetArgs { + /// Sets the numlock option to the given HOTP slot + #[structopt(short = "n", long)] + pub numlock: Option<u8>, + /// Unsets the numlock option + #[structopt(short = "N", long, conflicts_with("numlock"))] + pub no_numlock: bool, + /// Sets the capslock option to the given HOTP slot + #[structopt(short = "c", long)] + pub capslock: Option<u8>, + /// Unsets the capslock option + #[structopt(short = "C", long, conflicts_with("capslock"))] + pub no_capslock: bool, + /// Sets the scrollock option to the given HOTP slot + #[structopt(short = "s", long)] + pub scrollock: Option<u8>, + /// Unsets the scrollock option + #[structopt(short = "S", long, conflicts_with("scrollock"))] + pub no_scrollock: bool, + /// Requires the user PIN to generate one-time passwords + #[structopt(short = "o", long)] + pub otp_pin: bool, + /// Allows one-time password generation without PIN + #[structopt(short = "O", long, conflicts_with("otp-pin"))] + pub no_otp_pin: bool, +} + +#[derive(Clone, Copy, Debug)] +pub enum ConfigOption<T> { + Enable(T), + Disable, + Ignore, +} + +impl<T> ConfigOption<T> { + pub fn try_from(disable: bool, value: Option<T>, name: &'static str) -> Result<Self, Error> { + 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<T>) -> Option<T> { + match self { + ConfigOption::Enable(value) => Some(value), + ConfigOption::Disable => None, + ConfigOption::Ignore => default, + } + } +} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct EncryptedArgs { + #[structopt(subcommand)] + subcmd: EncryptedCommand, +} + +Command! {EncryptedCommand, [ + /// Closes the encrypted volume on a Nitrokey Storage + Close => commands::encrypted_close, + /// Opens the encrypted volume on a Nitrokey Storage + Open => commands::encrypted_open, +]} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct HiddenArgs { + #[structopt(subcommand)] + subcmd: HiddenCommand, +} + +Command! {HiddenCommand, [ + /// Closes the hidden volume on a Nitrokey Storage + Close => commands::hidden_close, + /// Creates a hidden volume on a Nitrokey Storage + Create(HiddenCreateArgs) => |ctx, args: HiddenCreateArgs| { + commands::hidden_create(ctx, args.slot, args.start, args.end) + }, + /// Opens the hidden volume on a Nitrokey Storage + Open => commands::hidden_open, +]} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct HiddenCreateArgs { + /// The hidden volume slot to use + pub slot: u8, + /// The start location of the hidden volume as a percentage of the encrypted volume's size (0-99) + pub start: u8, + /// The end location of the hidden volume as a percentage of the encrypted volume's size (1-100) + pub end: u8, +} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct ListArgs { + /// Only print the information that is available without connecting to a device + #[structopt(short, long)] + pub no_connect: bool, +} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct OtpArgs { + #[structopt(subcommand)] + subcmd: OtpCommand, +} + +Command! {OtpCommand, [ + /// Clears a one-time password slot + Clear(OtpClearArgs) => |ctx, args: OtpClearArgs| { + commands::otp_clear(ctx, args.slot, args.algorithm) + }, + /// Generates a one-time password + Get(OtpGetArgs) => |ctx, args: OtpGetArgs| { + commands::otp_get(ctx, args.slot, args.algorithm, args.time) + }, + /// Configures a one-time password slot + Set(OtpSetArgs) => args::otp_set, + /// Prints the status of the one-time password slots + Status(OtpStatusArgs) => |ctx, args: OtpStatusArgs| commands::otp_status(ctx, args.all), +]} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct OtpClearArgs { + /// The OTP algorithm to use + #[structopt(short, long, default_value = OtpAlgorithm::Totp.as_ref(), + possible_values = &OtpAlgorithm::all_str())] + pub algorithm: OtpAlgorithm, + /// The OTP slot to clear + pub slot: u8, +} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct OtpGetArgs { + /// The OTP algorithm to use + #[structopt(short, long, default_value = OtpAlgorithm::Totp.as_ref(), + possible_values = &OtpAlgorithm::all_str())] + pub algorithm: OtpAlgorithm, + /// The time to use for TOTP generation (Unix timestamp) [default: system time] + #[structopt(short, long)] + pub time: Option<u64>, + /// The OTP slot to use + pub slot: u8, +} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct OtpSetArgs { + /// The OTP algorithm to use + #[structopt(short, long, default_value = OtpAlgorithm::Totp.as_ref(), + possible_values = &OtpAlgorithm::all_str())] + pub algorithm: OtpAlgorithm, + /// The number of digits to use for the one-time password + #[structopt(short, long, default_value = OtpMode::SixDigits.as_ref(), + possible_values = &OtpMode::all_str())] + pub digits: OtpMode, + /// The counter value for HOTP + #[structopt(short, long, default_value = "0")] + pub counter: u64, + /// The time window for TOTP + #[structopt(short, long, default_value = "30")] + pub time_window: u16, + /// The format of the secret + #[structopt(short, long, default_value = OtpSecretFormat::Hex.as_ref(), + possible_values = &OtpSecretFormat::all_str())] + pub format: OtpSecretFormat, + /// The OTP slot to use + pub slot: u8, + /// The name of the slot + pub name: String, + /// The secret to store on the slot as a hexadecimal string (or in the format set with the + /// --format option) + pub secret: String, +} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct OtpStatusArgs { + /// Shows slots that are not programmed + #[structopt(short, long)] + pub all: bool, +} + +Enum! {OtpAlgorithm, [ + Hotp => "hotp", + Totp => "totp", +]} + +Enum! {OtpMode, [ + SixDigits => "6", + EightDigits => "8", +]} + +impl From<OtpMode> for nitrokey::OtpMode { + fn from(mode: OtpMode) -> Self { + match mode { + OtpMode::SixDigits => nitrokey::OtpMode::SixDigits, + OtpMode::EightDigits => nitrokey::OtpMode::EightDigits, + } + } +} + +Enum! {OtpSecretFormat, [ + Ascii => "ascii", + Base32 => "base32", + Hex => "hex", +]} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct PinArgs { + #[structopt(subcommand)] + subcmd: PinCommand, +} + +Command! {PinCommand, [ + /// Clears the cached PINs + Clear => commands::pin_clear, + /// Changes a PIN + Set(PinSetArgs) => |ctx, args: PinSetArgs| commands::pin_set(ctx, args.pintype), + /// Unblocks and resets the user PIN + Unblock => commands::pin_unblock, +]} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct PinSetArgs { + /// The PIN type to change + #[structopt(name = "type", possible_values = &pinentry::PinType::all_str())] + pub pintype: pinentry::PinType, +} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct PwsArgs { + #[structopt(subcommand)] + subcmd: PwsCommand, +} + +Command! {PwsCommand, [ + /// Clears a password safe slot + Clear(PwsClearArgs) => |ctx, args: PwsClearArgs| commands::pws_clear(ctx, args.slot), + /// Reads a password safe slot + Get(PwsGetArgs) => |ctx, args: PwsGetArgs| { + commands::pws_get(ctx, args.slot, args.name, args.login, args.password, args.quiet) + }, + /// Writes a password safe slot + Set(PwsSetArgs) => |ctx, args: PwsSetArgs| { + commands::pws_set(ctx, args.slot, &args.name, &args.login, &args.password) + }, + /// Prints the status of the password safe slots + Status(PwsStatusArgs) => |ctx, args: PwsStatusArgs| commands::pws_status(ctx, args.all), +]} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct PwsClearArgs { + /// The PWS slot to clear + pub slot: u8, +} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct PwsGetArgs { + /// Shows the name stored on the slot + #[structopt(short, long)] + pub name: bool, + /// Shows the login stored on the slot + #[structopt(short, long)] + pub login: bool, + /// Shows the password stored on the slot + #[structopt(short, long)] + pub password: bool, + /// Prints the stored data without description + #[structopt(short, long)] + pub quiet: bool, + /// The PWS slot to read + pub slot: u8, +} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct PwsSetArgs { + /// The PWS slot to write + pub slot: u8, + /// The name to store on the slot + pub name: String, + /// The login to store on the slot + pub login: String, + /// The password to store on the slot + pub password: String, +} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct PwsStatusArgs { + /// Shows slots that are not programmed + #[structopt(short, long)] + pub all: bool, +} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct UnencryptedArgs { + #[structopt(subcommand)] + subcmd: UnencryptedCommand, +} + +Command! {UnencryptedCommand, [ + /// Changes the configuration of the unencrypted volume on a Nitrokey Storage + Set(UnencryptedSetArgs) => |ctx, args: UnencryptedSetArgs| { + commands::unencrypted_set(ctx, args.mode) + }, +]} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct UnencryptedSetArgs { + /// The mode to change to + #[structopt(name = "type", possible_values = &UnencryptedVolumeMode::all_str())] + pub mode: UnencryptedVolumeMode, +} + +Enum! {UnencryptedVolumeMode, [ + ReadWrite => "read-write", + ReadOnly => "read-only", +]} diff --git a/src/arg_util.rs b/src/arg_util.rs index 930e470..c16c326 100644 --- a/src/arg_util.rs +++ b/src/arg_util.rs @@ -35,7 +35,7 @@ macro_rules! tr { macro_rules! Command { ( $name:ident, [ $( $(#[$doc:meta])* $var:ident$(($inner:ty))? => $exec:expr, ) *] ) => { #[derive(Debug, PartialEq, structopt::StructOpt)] - enum $name { + pub enum $name { $( $(#[$doc])* $var$(($inner))?, @@ -44,7 +44,7 @@ macro_rules! Command { #[allow(unused_qualifications)] impl $name { - fn execute( + pub fn execute( self, ctx: &mut crate::args::ExecCtx<'_>, ) -> crate::Result<()> { diff --git a/src/args.rs b/src/args.rs index df93814..720fabf 100644 --- a/src/args.rs +++ b/src/args.rs @@ -20,11 +20,10 @@ use std::ffi; use std::io; use std::result; -use std::str; +use crate::arg_defs; use crate::commands; use crate::error::Error; -use crate::pinentry; use crate::RunCtx; type Result<T> = result::Result<T, Error>; @@ -51,7 +50,7 @@ where /// A command execution context that captures additional data pertaining /// the command execution. pub struct ExecCtx<'io> { - pub model: Option<DeviceModel>, + pub model: Option<arg_defs::DeviceModel>, pub stdout: &'io mut dyn io::Write, pub stderr: &'io mut dyn io::Write, pub admin_pin: Option<ffi::OsString>, @@ -69,152 +68,10 @@ impl<'io> Stdio for ExecCtx<'io> { } } -/// Provides access to a Nitrokey device -#[derive(structopt::StructOpt)] -#[structopt(name = "nitrocli")] -struct Args { - /// Increases the log level (can be supplied multiple times) - #[structopt(short, long, global = true, parse(from_occurrences))] - verbose: u8, - /// Selects the device model to connect to - #[structopt(short, long, global = true, possible_values = &DeviceModel::all_str())] - model: Option<DeviceModel>, - #[structopt(subcommand)] - cmd: Command, -} - -/// The available Nitrokey models. -#[allow(unused_doc_comments)] -Enum! {DeviceModel, [ - Pro => "pro", - Storage => "storage", -]} - -impl DeviceModel { - pub fn as_user_facing_str(&self) -> &str { - match self { - DeviceModel::Pro => "Pro", - DeviceModel::Storage => "Storage", - } - } -} - -impl From<DeviceModel> for nitrokey::Model { - fn from(model: DeviceModel) -> nitrokey::Model { - match model { - DeviceModel::Pro => nitrokey::Model::Pro, - DeviceModel::Storage => nitrokey::Model::Storage, - } - } -} - -/// A top-level command for nitrocli. -#[allow(unused_doc_comments)] -Command! {Command, [ - /// Reads or writes the device configuration - Config(ConfigArgs) => |ctx, args: ConfigArgs| args.subcmd.execute(ctx), - /// Interacts with the device's encrypted volume - Encrypted(EncryptedArgs) => |ctx, args: EncryptedArgs| args.subcmd.execute(ctx), - /// Interacts with the device's hidden volume - Hidden(HiddenArgs) => |ctx, args: HiddenArgs| args.subcmd.execute(ctx), - /// Lists the attached Nitrokey devices - List(ListArgs) => |ctx, args: ListArgs| commands::list(ctx, args.no_connect), - /// Locks the connected Nitrokey device - Lock => commands::lock, - /// Accesses one-time passwords - Otp(OtpArgs) => |ctx, args: OtpArgs| args.subcmd.execute(ctx), - /// Manages the Nitrokey PINs - Pin(PinArgs) => |ctx, args: PinArgs| args.subcmd.execute(ctx), - /// Accesses the password safe - Pws(PwsArgs) => |ctx, args: PwsArgs| args.subcmd.execute(ctx), - /// Performs a factory reset - Reset => commands::reset, - /// Prints the status of the connected Nitrokey device - Status => commands::status, - /// Interacts with the device's unencrypted volume - Unencrypted(UnencryptedArgs) => |ctx, args: UnencryptedArgs| args.subcmd.execute(ctx), -]} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct ConfigArgs { - #[structopt(subcommand)] - subcmd: ConfigCommand, -} - -Command! {ConfigCommand, [ - /// Prints the Nitrokey configuration - Get => commands::config_get, - /// Changes the Nitrokey configuration - Set(ConfigSetArgs) => config_set, -]} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct ConfigSetArgs { - /// Sets the numlock option to the given HOTP slot - #[structopt(short = "n", long)] - numlock: Option<u8>, - /// Unsets the numlock option - #[structopt(short = "N", long, conflicts_with("numlock"))] - no_numlock: bool, - /// Sets the capslock option to the given HOTP slot - #[structopt(short = "c", long)] - capslock: Option<u8>, - /// Unsets the capslock option - #[structopt(short = "C", long, conflicts_with("capslock"))] - no_capslock: bool, - /// Sets the scrollock option to the given HOTP slot - #[structopt(short = "s", long)] - scrollock: Option<u8>, - /// Unsets the scrollock option - #[structopt(short = "S", long, conflicts_with("scrollock"))] - no_scrollock: bool, - /// Requires the user PIN to generate one-time passwords - #[structopt(short = "o", long)] - otp_pin: bool, - /// Allows one-time password generation without PIN - #[structopt(short = "O", long, conflicts_with("otp-pin"))] - no_otp_pin: bool, -} - -#[derive(Clone, Copy, Debug)] -pub enum ConfigOption<T> { - Enable(T), - Disable, - Ignore, -} - -impl<T> ConfigOption<T> { - fn try_from(disable: bool, value: Option<T>, name: &'static str) -> Result<Self> { - 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<T>) -> Option<T> { - match self { - ConfigOption::Enable(value) => Some(value), - ConfigOption::Disable => None, - ConfigOption::Ignore => default, - } - } -} - -fn config_set(ctx: &mut ExecCtx<'_>, args: ConfigSetArgs) -> Result<()> { - let numlock = ConfigOption::try_from(args.no_numlock, args.numlock, "numlock")?; - let capslock = ConfigOption::try_from(args.no_capslock, args.capslock, "capslock")?; - let scrollock = ConfigOption::try_from(args.no_scrollock, args.scrollock, "scrollock")?; +pub fn config_set(ctx: &mut ExecCtx<'_>, args: arg_defs::ConfigSetArgs) -> Result<()> { + let numlock = arg_defs::ConfigOption::try_from(args.no_numlock, args.numlock, "numlock")?; + let capslock = arg_defs::ConfigOption::try_from(args.no_capslock, args.capslock, "capslock")?; + let scrollock = arg_defs::ConfigOption::try_from(args.no_scrollock, args.scrollock, "scrollock")?; let otp_pin = if args.otp_pin { Some(true) } else if args.no_otp_pin { @@ -225,159 +82,7 @@ fn config_set(ctx: &mut ExecCtx<'_>, args: ConfigSetArgs) -> Result<()> { commands::config_set(ctx, numlock, capslock, scrollock, otp_pin) } -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct EncryptedArgs { - #[structopt(subcommand)] - subcmd: EncryptedCommand, -} - -Command! {EncryptedCommand, [ - /// Closes the encrypted volume on a Nitrokey Storage - Close => commands::encrypted_close, - /// Opens the encrypted volume on a Nitrokey Storage - Open => commands::encrypted_open, -]} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct HiddenArgs { - #[structopt(subcommand)] - subcmd: HiddenCommand, -} - -Command! {HiddenCommand, [ - /// Closes the hidden volume on a Nitrokey Storage - Close => commands::hidden_close, - /// Creates a hidden volume on a Nitrokey Storage - Create(HiddenCreateArgs) => |ctx, args: HiddenCreateArgs| { - commands::hidden_create(ctx, args.slot, args.start, args.end) - }, - /// Opens the hidden volume on a Nitrokey Storage - Open => commands::hidden_open, -]} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct HiddenCreateArgs { - /// The hidden volume slot to use - slot: u8, - /// The start location of the hidden volume as a percentage of the encrypted volume's size (0-99) - start: u8, - /// The end location of the hidden volume as a percentage of the encrypted volume's size (1-100) - end: u8, -} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct ListArgs { - /// Only print the information that is available without connecting to a device - #[structopt(short, long)] - no_connect: bool, -} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct OtpArgs { - #[structopt(subcommand)] - subcmd: OtpCommand, -} - -Command! {OtpCommand, [ - /// Clears a one-time password slot - Clear(OtpClearArgs) => |ctx, args: OtpClearArgs| { - commands::otp_clear(ctx, args.slot, args.algorithm) - }, - /// Generates a one-time password - Get(OtpGetArgs) => |ctx, args: OtpGetArgs| { - commands::otp_get(ctx, args.slot, args.algorithm, args.time) - }, - /// Configures a one-time password slot - Set(OtpSetArgs) => otp_set, - /// Prints the status of the one-time password slots - Status(OtpStatusArgs) => |ctx, args: OtpStatusArgs| commands::otp_status(ctx, args.all), -]} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct OtpClearArgs { - /// The OTP algorithm to use - #[structopt(short, long, default_value = OtpAlgorithm::Totp.as_ref(), - possible_values = &OtpAlgorithm::all_str())] - algorithm: OtpAlgorithm, - /// The OTP slot to clear - slot: u8, -} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct OtpGetArgs { - /// The OTP algorithm to use - #[structopt(short, long, default_value = OtpAlgorithm::Totp.as_ref(), - possible_values = &OtpAlgorithm::all_str())] - algorithm: OtpAlgorithm, - /// The time to use for TOTP generation (Unix timestamp) [default: system time] - #[structopt(short, long)] - time: Option<u64>, - /// The OTP slot to use - slot: u8, -} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct OtpSetArgs { - /// The OTP algorithm to use - #[structopt(short, long, default_value = OtpAlgorithm::Totp.as_ref(), - possible_values = &OtpAlgorithm::all_str())] - algorithm: OtpAlgorithm, - /// The number of digits to use for the one-time password - #[structopt(short, long, default_value = OtpMode::SixDigits.as_ref(), - possible_values = &OtpMode::all_str())] - digits: OtpMode, - /// The counter value for HOTP - #[structopt(short, long, default_value = "0")] - counter: u64, - /// The time window for TOTP - #[structopt(short, long, default_value = "30")] - time_window: u16, - /// The format of the secret - #[structopt(short, long, default_value = OtpSecretFormat::Hex.as_ref(), - possible_values = &OtpSecretFormat::all_str())] - format: OtpSecretFormat, - /// The OTP slot to use - slot: u8, - /// The name of the slot - name: String, - /// The secret to store on the slot as a hexadecimal string (or in the format set with the - /// --format option) - secret: String, -} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct OtpStatusArgs { - /// Shows slots that are not programmed - #[structopt(short, long)] - all: bool, -} - -Enum! {OtpAlgorithm, [ - Hotp => "hotp", - Totp => "totp", -]} - -Enum! {OtpMode, [ - SixDigits => "6", - EightDigits => "8", -]} - -impl From<OtpMode> for nitrokey::OtpMode { - fn from(mode: OtpMode) -> Self { - match mode { - OtpMode::SixDigits => nitrokey::OtpMode::SixDigits, - OtpMode::EightDigits => nitrokey::OtpMode::EightDigits, - } - } -} - -Enum! {OtpSecretFormat, [ - Ascii => "ascii", - Base32 => "base32", - Hex => "hex", -]} - -fn otp_set(ctx: &mut ExecCtx<'_>, args: OtpSetArgs) -> Result<()> { +pub fn otp_set(ctx: &mut ExecCtx<'_>, args: arg_defs::OtpSetArgs) -> Result<()> { let data = nitrokey::OtpSlotData { number: args.slot, name: args.name, @@ -396,122 +101,11 @@ fn otp_set(ctx: &mut ExecCtx<'_>, args: OtpSetArgs) -> Result<()> { ) } -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct PinArgs { - #[structopt(subcommand)] - subcmd: PinCommand, -} - -Command! {PinCommand, [ - /// Clears the cached PINs - Clear => commands::pin_clear, - /// Changes a PIN - Set(PinSetArgs) => |ctx, args: PinSetArgs| commands::pin_set(ctx, args.pintype), - /// Unblocks and resets the user PIN - Unblock => commands::pin_unblock, -]} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct PinSetArgs { - /// The PIN type to change - #[structopt(name = "type", possible_values = &pinentry::PinType::all_str())] - pintype: pinentry::PinType, -} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct PwsArgs { - #[structopt(subcommand)] - subcmd: PwsCommand, -} - -Command! {PwsCommand, [ - /// Clears a password safe slot - Clear(PwsClearArgs) => |ctx, args: PwsClearArgs| commands::pws_clear(ctx, args.slot), - /// Reads a password safe slot - Get(PwsGetArgs) => |ctx, args: PwsGetArgs| { - commands::pws_get(ctx, args.slot, args.name, args.login, args.password, args.quiet) - }, - /// Writes a password safe slot - Set(PwsSetArgs) => |ctx, args: PwsSetArgs| { - commands::pws_set(ctx, args.slot, &args.name, &args.login, &args.password) - }, - /// Prints the status of the password safe slots - Status(PwsStatusArgs) => |ctx, args: PwsStatusArgs| commands::pws_status(ctx, args.all), -]} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct PwsClearArgs { - /// The PWS slot to clear - slot: u8, -} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct PwsGetArgs { - /// Shows the name stored on the slot - #[structopt(short, long)] - name: bool, - /// Shows the login stored on the slot - #[structopt(short, long)] - login: bool, - /// Shows the password stored on the slot - #[structopt(short, long)] - password: bool, - /// Prints the stored data without description - #[structopt(short, long)] - quiet: bool, - /// The PWS slot to read - slot: u8, -} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct PwsSetArgs { - /// The PWS slot to write - slot: u8, - /// The name to store on the slot - name: String, - /// The login to store on the slot - login: String, - /// The password to store on the slot - password: String, -} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct PwsStatusArgs { - /// Shows slots that are not programmed - #[structopt(short, long)] - all: bool, -} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct UnencryptedArgs { - #[structopt(subcommand)] - subcmd: UnencryptedCommand, -} - -Command! {UnencryptedCommand, [ - /// Changes the configuration of the unencrypted volume on a Nitrokey Storage - Set(UnencryptedSetArgs) => |ctx, args: UnencryptedSetArgs| { - commands::unencrypted_set(ctx, args.mode) - }, -]} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -struct UnencryptedSetArgs { - /// The mode to change to - #[structopt(name = "type", possible_values = &UnencryptedVolumeMode::all_str())] - mode: UnencryptedVolumeMode, -} - -Enum! {UnencryptedVolumeMode, [ - ReadWrite => "read-write", - ReadOnly => "read-only", -]} - /// Parse the command-line arguments and execute the selected command. pub(crate) fn handle_arguments(ctx: &mut RunCtx<'_>, args: Vec<String>) -> Result<()> { use structopt::StructOpt; - match Args::from_iter_safe(args.iter()) { + match arg_defs::Args::from_iter_safe(args.iter()) { Ok(args) => { let mut ctx = ExecCtx { model: args.model, diff --git a/src/commands.rs b/src/commands.rs index 08dad04..31f4c26 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -30,6 +30,7 @@ use nitrokey::Device; use nitrokey::GenerateOtp; use nitrokey::GetPasswordSafe; +use crate::arg_defs; use crate::args; use crate::error; use crate::error::Error; @@ -87,7 +88,7 @@ where set_log_level(ctx); if let Some(model) = ctx.model { - if model != args::DeviceModel::Storage { + if model != arg_defs::DeviceModel::Storage { return Err(Error::from( "This command is only available on the Nitrokey Storage", )); @@ -441,13 +442,13 @@ pub fn reset(ctx: &mut args::ExecCtx<'_>) -> Result<()> { /// Change the configuration of the unencrypted volume. pub fn unencrypted_set( ctx: &mut args::ExecCtx<'_>, - mode: args::UnencryptedVolumeMode, + mode: arg_defs::UnencryptedVolumeMode, ) -> Result<()> { with_storage_device(ctx, |ctx, mut device| { let pin_entry = pinentry::PinEntry::from(pinentry::PinType::Admin, &device)?; let mode = match mode { - args::UnencryptedVolumeMode::ReadWrite => nitrokey::VolumeMode::ReadWrite, - args::UnencryptedVolumeMode::ReadOnly => nitrokey::VolumeMode::ReadOnly, + arg_defs::UnencryptedVolumeMode::ReadWrite => nitrokey::VolumeMode::ReadWrite, + arg_defs::UnencryptedVolumeMode::ReadOnly => nitrokey::VolumeMode::ReadOnly, }; // The unencrypted volume may reconnect, so be sure to flush caches to @@ -579,9 +580,9 @@ pub fn config_get(ctx: &mut args::ExecCtx<'_>) -> Result<()> { /// Write the Nitrokey configuration. pub fn config_set( ctx: &mut args::ExecCtx<'_>, - numlock: args::ConfigOption<u8>, - capslock: args::ConfigOption<u8>, - scrollock: args::ConfigOption<u8>, + numlock: arg_defs::ConfigOption<u8>, + capslock: arg_defs::ConfigOption<u8>, + scrollock: arg_defs::ConfigOption<u8>, user_password: Option<bool>, ) -> Result<()> { with_device(ctx, |ctx, device| { @@ -610,13 +611,13 @@ pub fn lock(ctx: &mut args::ExecCtx<'_>) -> Result<()> { }) } -fn get_otp<T>(slot: u8, algorithm: args::OtpAlgorithm, device: &mut T) -> Result<String> +fn get_otp<T>(slot: u8, algorithm: arg_defs::OtpAlgorithm, device: &mut T) -> Result<String> where T: GenerateOtp, { match algorithm { - args::OtpAlgorithm::Hotp => device.get_hotp_code(slot), - args::OtpAlgorithm::Totp => device.get_totp_code(slot), + arg_defs::OtpAlgorithm::Hotp => device.get_hotp_code(slot), + arg_defs::OtpAlgorithm::Totp => device.get_totp_code(slot), } .map_err(|err| get_error("Could not generate OTP", err)) } @@ -632,11 +633,11 @@ fn get_unix_timestamp() -> Result<u64> { pub fn otp_get( ctx: &mut args::ExecCtx<'_>, slot: u8, - algorithm: args::OtpAlgorithm, + algorithm: arg_defs::OtpAlgorithm, time: Option<u64>, ) -> Result<()> { with_device(ctx, |ctx, mut device| { - if algorithm == args::OtpAlgorithm::Totp { + if algorithm == arg_defs::OtpAlgorithm::Totp { device .set_time( match time { @@ -696,16 +697,16 @@ fn prepare_base32_secret(secret: &str) -> Result<String> { pub fn otp_set( ctx: &mut args::ExecCtx<'_>, mut data: nitrokey::OtpSlotData, - algorithm: args::OtpAlgorithm, + algorithm: arg_defs::OtpAlgorithm, counter: u64, time_window: u16, - secret_format: args::OtpSecretFormat, + secret_format: arg_defs::OtpSecretFormat, ) -> Result<()> { with_device(ctx, |ctx, device| { let secret = match secret_format { - args::OtpSecretFormat::Ascii => prepare_ascii_secret(&data.secret)?, - args::OtpSecretFormat::Base32 => prepare_base32_secret(&data.secret)?, - args::OtpSecretFormat::Hex => { + arg_defs::OtpSecretFormat::Ascii => prepare_ascii_secret(&data.secret)?, + arg_defs::OtpSecretFormat::Base32 => prepare_base32_secret(&data.secret)?, + arg_defs::OtpSecretFormat::Hex => { // We need to ensure to provide a string with an even number of // characters in it, just because that's what libnitrokey // expects. So prepend a '0' if that is not the case. @@ -721,8 +722,8 @@ pub fn otp_set( let data = nitrokey::OtpSlotData { secret, ..data }; let mut device = authenticate_admin(ctx, device)?; match algorithm { - args::OtpAlgorithm::Hotp => device.write_hotp_slot(data, counter), - args::OtpAlgorithm::Totp => device.write_totp_slot(data, time_window), + arg_defs::OtpAlgorithm::Hotp => device.write_hotp_slot(data, counter), + arg_defs::OtpAlgorithm::Totp => device.write_totp_slot(data, time_window), } .map_err(|err| get_error("Could not write OTP slot", err))?; Ok(()) @@ -733,13 +734,13 @@ pub fn otp_set( pub fn otp_clear( ctx: &mut args::ExecCtx<'_>, slot: u8, - algorithm: args::OtpAlgorithm, + algorithm: arg_defs::OtpAlgorithm, ) -> Result<()> { with_device(ctx, |ctx, device| { let mut device = authenticate_admin(ctx, device)?; match algorithm { - args::OtpAlgorithm::Hotp => device.erase_hotp_slot(slot), - args::OtpAlgorithm::Totp => device.erase_totp_slot(slot), + arg_defs::OtpAlgorithm::Hotp => device.erase_hotp_slot(slot), + arg_defs::OtpAlgorithm::Totp => device.erase_totp_slot(slot), } .map_err(|err| get_error("Could not clear OTP slot", err))?; Ok(()) @@ -748,15 +749,15 @@ pub fn otp_clear( fn print_otp_status( ctx: &mut args::ExecCtx<'_>, - algorithm: args::OtpAlgorithm, + algorithm: arg_defs::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), + arg_defs::OtpAlgorithm::Hotp => device.get_hotp_slot_name(slot), + arg_defs::OtpAlgorithm::Totp => device.get_totp_slot_name(slot), }; slot = match slot.checked_add(1) { Some(slot) => slot, @@ -784,8 +785,8 @@ fn print_otp_status( pub fn otp_status(ctx: &mut args::ExecCtx<'_>, all: bool) -> Result<()> { with_device(ctx, |ctx, device| { println!(ctx, "alg\tslot\tname")?; - print_otp_status(ctx, args::OtpAlgorithm::Hotp, &device, all)?; - print_otp_status(ctx, args::OtpAlgorithm::Totp, &device, all)?; + print_otp_status(ctx, arg_defs::OtpAlgorithm::Hotp, &device, all)?; + print_otp_status(ctx, arg_defs::OtpAlgorithm::Totp, &device, all)?; Ok(()) }) } diff --git a/src/main.rs b/src/main.rs index e42bf4d..89c9d67 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,6 +68,7 @@ mod redefine; #[macro_use] mod arg_util; +mod arg_defs; mod args; mod commands; mod error; diff --git a/src/tests/otp.rs b/src/tests/otp.rs index 0ccecf9..4ce1156 100644 --- a/src/tests/otp.rs +++ b/src/tests/otp.rs @@ -19,7 +19,7 @@ use super::*; -use crate::args; +use crate::arg_defs; #[test_device] fn set_invalid_slot_raw(model: nitrokey::Model) { @@ -101,8 +101,8 @@ fn set_get_totp(model: nitrokey::Model) -> crate::Result<()> { #[test_device] fn set_totp_uneven_chars(model: nitrokey::Model) -> crate::Result<()> { let secrets = [ - (args::OtpSecretFormat::Hex, "123"), - (args::OtpSecretFormat::Base32, "FBILDWWGA2"), + (arg_defs::OtpSecretFormat::Hex, "123"), + (arg_defs::OtpSecretFormat::Base32, "FBILDWWGA2"), ]; for (format, secret) in &secrets { |