From f2ace56200f0229a4dcbd1767f48a39e1daa343d Mon Sep 17 00:00:00 2001 From: Daniel Mueller Date: Sat, 11 Apr 2020 15:00:06 -0700 Subject: Rename arg_defs.rs to args.rs We have never been fully satisfied with the name arg_defs. Now that we have gotten rid of the formerly used args module, this change renames arg_defs to args. --- src/arg_defs.rs | 433 ------------------------------------------------------- src/args.rs | 433 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/commands.rs | 95 ++++++------ src/main.rs | 6 +- src/pinentry.rs | 24 +-- src/tests/otp.rs | 8 +- 6 files changed, 498 insertions(+), 501 deletions(-) delete mode 100644 src/arg_defs.rs create mode 100644 src/args.rs (limited to 'src') diff --git a/src/arg_defs.rs b/src/arg_defs.rs deleted file mode 100644 index 133ef19..0000000 --- a/src/arg_defs.rs +++ /dev/null @@ -1,433 +0,0 @@ -// 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 . * -// ************************************************************************* - -/// 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, - #[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 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| crate::commands::list(ctx, args.no_connect), - /// Locks the connected Nitrokey device - Lock => crate::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 => crate::commands::reset, - /// Prints the status of the connected Nitrokey device - Status => crate::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 => crate::commands::config_get, - /// Changes the Nitrokey configuration - Set(ConfigSetArgs) => crate::commands::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, - /// 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, - /// 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, - /// 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 { - Enable(T), - Disable, - Ignore, -} - -impl ConfigOption { - pub fn try_from(disable: bool, value: Option, name: &'static str) -> Result { - if disable { - if value.is_some() { - Err(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, PartialEq, structopt::StructOpt)] -pub struct EncryptedArgs { - #[structopt(subcommand)] - subcmd: EncryptedCommand, -} - -Command! {EncryptedCommand, [ - /// Closes the encrypted volume on a Nitrokey Storage - Close => crate::commands::encrypted_close, - /// Opens the encrypted volume on a Nitrokey Storage - Open => crate::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 => crate::commands::hidden_close, - /// Creates a hidden volume on a Nitrokey Storage - Create(HiddenCreateArgs) => |ctx, args: HiddenCreateArgs| { - crate::commands::hidden_create(ctx, args.slot, args.start, args.end) - }, - /// Opens the hidden volume on a Nitrokey Storage - Open => crate::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| { - crate::commands::otp_clear(ctx, args.slot, args.algorithm) - }, - /// Generates a one-time password - Get(OtpGetArgs) => |ctx, args: OtpGetArgs| { - crate::commands::otp_get(ctx, args.slot, args.algorithm, args.time) - }, - /// Configures a one-time password slot - Set(OtpSetArgs) => crate::commands::otp_set, - /// Prints the status of the one-time password slots - Status(OtpStatusArgs) => |ctx, args: OtpStatusArgs| crate::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, - /// 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 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 => crate::commands::pin_clear, - /// Changes a PIN - Set(PinSetArgs) => |ctx, args: PinSetArgs| crate::commands::pin_set(ctx, args.pintype), - /// Unblocks and resets the user PIN - Unblock => crate::commands::pin_unblock, -]} - -/// PIN type requested from pinentry. -/// -/// The available PIN types correspond to the PIN types used by the Nitrokey devices: user and -/// admin. -#[allow(unused_doc_comments)] -Enum! {PinType, [ - Admin => "admin", - User => "user", -]} - -#[derive(Debug, PartialEq, structopt::StructOpt)] -pub struct PinSetArgs { - /// The PIN type to change - #[structopt(name = "type", possible_values = &PinType::all_str())] - pub pintype: 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| crate::commands::pws_clear(ctx, args.slot), - /// Reads a password safe slot - Get(PwsGetArgs) => |ctx, args: PwsGetArgs| { - crate::commands::pws_get(ctx, args.slot, args.name, args.login, args.password, args.quiet) - }, - /// Writes a password safe slot - Set(PwsSetArgs) => |ctx, args: PwsSetArgs| { - crate::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| crate::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| { - crate::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/args.rs b/src/args.rs new file mode 100644 index 0000000..20392a8 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,433 @@ +// args.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 . * +// ************************************************************************* + +/// 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, + #[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 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| crate::commands::list(ctx, args.no_connect), + /// Locks the connected Nitrokey device + Lock => crate::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 => crate::commands::reset, + /// Prints the status of the connected Nitrokey device + Status => crate::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 => crate::commands::config_get, + /// Changes the Nitrokey configuration + Set(ConfigSetArgs) => crate::commands::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, + /// 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, + /// 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, + /// 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 { + Enable(T), + Disable, + Ignore, +} + +impl ConfigOption { + pub fn try_from(disable: bool, value: Option, name: &'static str) -> Result { + if disable { + if value.is_some() { + Err(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, PartialEq, structopt::StructOpt)] +pub struct EncryptedArgs { + #[structopt(subcommand)] + subcmd: EncryptedCommand, +} + +Command! {EncryptedCommand, [ + /// Closes the encrypted volume on a Nitrokey Storage + Close => crate::commands::encrypted_close, + /// Opens the encrypted volume on a Nitrokey Storage + Open => crate::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 => crate::commands::hidden_close, + /// Creates a hidden volume on a Nitrokey Storage + Create(HiddenCreateArgs) => |ctx, args: HiddenCreateArgs| { + crate::commands::hidden_create(ctx, args.slot, args.start, args.end) + }, + /// Opens the hidden volume on a Nitrokey Storage + Open => crate::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| { + crate::commands::otp_clear(ctx, args.slot, args.algorithm) + }, + /// Generates a one-time password + Get(OtpGetArgs) => |ctx, args: OtpGetArgs| { + crate::commands::otp_get(ctx, args.slot, args.algorithm, args.time) + }, + /// Configures a one-time password slot + Set(OtpSetArgs) => crate::commands::otp_set, + /// Prints the status of the one-time password slots + Status(OtpStatusArgs) => |ctx, args: OtpStatusArgs| crate::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, + /// 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 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 => crate::commands::pin_clear, + /// Changes a PIN + Set(PinSetArgs) => |ctx, args: PinSetArgs| crate::commands::pin_set(ctx, args.pintype), + /// Unblocks and resets the user PIN + Unblock => crate::commands::pin_unblock, +]} + +/// PIN type requested from pinentry. +/// +/// The available PIN types correspond to the PIN types used by the Nitrokey devices: user and +/// admin. +#[allow(unused_doc_comments)] +Enum! {PinType, [ + Admin => "admin", + User => "user", +]} + +#[derive(Debug, PartialEq, structopt::StructOpt)] +pub struct PinSetArgs { + /// The PIN type to change + #[structopt(name = "type", possible_values = &PinType::all_str())] + pub pintype: 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| crate::commands::pws_clear(ctx, args.slot), + /// Reads a password safe slot + Get(PwsGetArgs) => |ctx, args: PwsGetArgs| { + crate::commands::pws_get(ctx, args.slot, args.name, args.login, args.password, args.quiet) + }, + /// Writes a password safe slot + Set(PwsSetArgs) => |ctx, args: PwsSetArgs| { + crate::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| crate::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| { + crate::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/commands.rs b/src/commands.rs index 208c039..a2b6004 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -31,7 +31,7 @@ use nitrokey::Device; use nitrokey::GenerateOtp; use nitrokey::GetPasswordSafe; -use crate::arg_defs; +use crate::args; use crate::error; use crate::error::Error; use crate::pinentry; @@ -89,7 +89,7 @@ where set_log_level(ctx); if let Some(model) = ctx.model { - if model != arg_defs::DeviceModel::Storage { + if model != args::DeviceModel::Storage { return Err(Error::from( "This command is only available on the Nitrokey Storage", )); @@ -109,7 +109,7 @@ where F: FnMut(&mut ExecCtx<'_>, nitrokey::PasswordSafe<'_, '_>) -> Result<()>, { with_device(ctx, |ctx, mut device| { - let pin_entry = pinentry::PinEntry::from(arg_defs::PinType::User, &device)?; + let pin_entry = pinentry::PinEntry::from(args::PinType::User, &device)?; try_with_pin_and_data( ctx, &pin_entry, @@ -133,7 +133,7 @@ where fn authenticate<'mgr, D, A, F>( ctx: &mut ExecCtx<'_>, device: D, - pin_type: arg_defs::PinType, + pin_type: args::PinType, msg: &'static str, op: F, ) -> Result @@ -154,7 +154,7 @@ where authenticate( ctx, device, - arg_defs::PinType::User, + args::PinType::User, "Could not authenticate as user", |_ctx, device, pin| device.authenticate_user(pin), ) @@ -168,7 +168,7 @@ where authenticate( ctx, device, - arg_defs::PinType::Admin, + args::PinType::Admin, "Could not authenticate as admin", |_ctx, device, pin| device.authenticate_admin(pin), ) @@ -256,8 +256,8 @@ where // Ideally we would not clone here, but that would require us to // restrict op to work with an immutable ExecCtx, which is not // possible given that some clients print data. - arg_defs::PinType::Admin => ctx.admin_pin.clone(), - arg_defs::PinType::User => ctx.user_pin.clone(), + args::PinType::Admin => ctx.admin_pin.clone(), + args::PinType::User => ctx.user_pin.clone(), }; if let Some(pin) = pin { @@ -410,7 +410,7 @@ pub fn list(ctx: &mut ExecCtx<'_>, no_connect: bool) -> Result<()> { /// Perform a factory reset. pub fn reset(ctx: &mut ExecCtx<'_>) -> Result<()> { with_device(ctx, |ctx, mut device| { - let pin_entry = pinentry::PinEntry::from(arg_defs::PinType::Admin, &device)?; + let pin_entry = pinentry::PinEntry::from(args::PinType::Admin, &device)?; // To force the user to enter the admin PIN before performing a // factory reset, we clear the pinentry cache for the admin PIN. @@ -432,12 +432,12 @@ pub fn reset(ctx: &mut ExecCtx<'_>) -> Result<()> { } /// Change the configuration of the unencrypted volume. -pub fn unencrypted_set(ctx: &mut ExecCtx<'_>, mode: arg_defs::UnencryptedVolumeMode) -> Result<()> { +pub fn unencrypted_set(ctx: &mut ExecCtx<'_>, mode: args::UnencryptedVolumeMode) -> Result<()> { with_storage_device(ctx, |ctx, mut device| { - let pin_entry = pinentry::PinEntry::from(arg_defs::PinType::Admin, &device)?; + let pin_entry = pinentry::PinEntry::from(args::PinType::Admin, &device)?; let mode = match mode { - arg_defs::UnencryptedVolumeMode::ReadWrite => nitrokey::VolumeMode::ReadWrite, - arg_defs::UnencryptedVolumeMode::ReadOnly => nitrokey::VolumeMode::ReadOnly, + args::UnencryptedVolumeMode::ReadWrite => nitrokey::VolumeMode::ReadWrite, + args::UnencryptedVolumeMode::ReadOnly => nitrokey::VolumeMode::ReadOnly, }; // The unencrypted volume may reconnect, so be sure to flush caches to @@ -456,7 +456,7 @@ pub fn unencrypted_set(ctx: &mut ExecCtx<'_>, mode: arg_defs::UnencryptedVolumeM /// Open the encrypted volume on the Nitrokey. pub fn encrypted_open(ctx: &mut ExecCtx<'_>) -> Result<()> { with_storage_device(ctx, |ctx, mut device| { - let pin_entry = pinentry::PinEntry::from(arg_defs::PinType::User, &device)?; + let pin_entry = pinentry::PinEntry::from(args::PinType::User, &device)?; // We may forcefully close a hidden volume, if active, so be sure to // flush caches to disk. @@ -567,10 +567,10 @@ pub fn config_get(ctx: &mut ExecCtx<'_>) -> Result<()> { } /// Write the Nitrokey configuration. -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")?; +pub fn config_set(ctx: &mut ExecCtx<'_>, args: args::ConfigSetArgs) -> Result<()> { + let numlock = args::ConfigOption::try_from(args.no_numlock, args.numlock, "numlock")?; + let capslock = args::ConfigOption::try_from(args.no_capslock, args.capslock, "capslock")?; + let scrollock = args::ConfigOption::try_from(args.no_scrollock, args.scrollock, "scrollock")?; let otp_pin = if args.otp_pin { Some(true) } else if args.no_otp_pin { @@ -605,13 +605,13 @@ pub fn lock(ctx: &mut ExecCtx<'_>) -> Result<()> { }) } -fn get_otp(slot: u8, algorithm: arg_defs::OtpAlgorithm, device: &mut T) -> Result +fn get_otp(slot: u8, algorithm: args::OtpAlgorithm, device: &mut T) -> Result where T: GenerateOtp, { match algorithm { - arg_defs::OtpAlgorithm::Hotp => device.get_hotp_code(slot), - arg_defs::OtpAlgorithm::Totp => device.get_totp_code(slot), + args::OtpAlgorithm::Hotp => device.get_hotp_code(slot), + args::OtpAlgorithm::Totp => device.get_totp_code(slot), } .map_err(|err| get_error("Could not generate OTP", err)) } @@ -627,11 +627,11 @@ fn get_unix_timestamp() -> Result { pub fn otp_get( ctx: &mut ExecCtx<'_>, slot: u8, - algorithm: arg_defs::OtpAlgorithm, + algorithm: args::OtpAlgorithm, time: Option, ) -> Result<()> { with_device(ctx, |ctx, mut device| { - if algorithm == arg_defs::OtpAlgorithm::Totp { + if algorithm == args::OtpAlgorithm::Totp { device .set_time( match time { @@ -688,7 +688,7 @@ fn prepare_base32_secret(secret: &str) -> Result { } /// Configure a one-time password slot on the Nitrokey device. -pub fn otp_set(ctx: &mut ExecCtx<'_>, mut args: arg_defs::OtpSetArgs) -> Result<()> { +pub fn otp_set(ctx: &mut ExecCtx<'_>, mut args: args::OtpSetArgs) -> Result<()> { let mut data = nitrokey::OtpSlotData { number: args.slot, name: mem::take(&mut args.name), @@ -700,9 +700,9 @@ pub fn otp_set(ctx: &mut ExecCtx<'_>, mut args: arg_defs::OtpSetArgs) -> Result< with_device(ctx, |ctx, device| { let secret = match args.format { - arg_defs::OtpSecretFormat::Ascii => prepare_ascii_secret(&data.secret)?, - arg_defs::OtpSecretFormat::Base32 => prepare_base32_secret(&data.secret)?, - arg_defs::OtpSecretFormat::Hex => { + args::OtpSecretFormat::Ascii => prepare_ascii_secret(&data.secret)?, + args::OtpSecretFormat::Base32 => prepare_base32_secret(&data.secret)?, + args::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. @@ -718,8 +718,8 @@ pub fn otp_set(ctx: &mut ExecCtx<'_>, mut args: arg_defs::OtpSetArgs) -> Result< let data = nitrokey::OtpSlotData { secret, ..data }; let mut device = authenticate_admin(ctx, device)?; match args.algorithm { - arg_defs::OtpAlgorithm::Hotp => device.write_hotp_slot(data, args.counter), - arg_defs::OtpAlgorithm::Totp => device.write_totp_slot(data, args.time_window), + args::OtpAlgorithm::Hotp => device.write_hotp_slot(data, args.counter), + args::OtpAlgorithm::Totp => device.write_totp_slot(data, args.time_window), } .map_err(|err| get_error("Could not write OTP slot", err))?; Ok(()) @@ -727,12 +727,12 @@ pub fn otp_set(ctx: &mut ExecCtx<'_>, mut args: arg_defs::OtpSetArgs) -> Result< } /// Clear an OTP slot. -pub fn otp_clear(ctx: &mut ExecCtx<'_>, slot: u8, algorithm: arg_defs::OtpAlgorithm) -> Result<()> { +pub fn otp_clear(ctx: &mut ExecCtx<'_>, slot: u8, algorithm: args::OtpAlgorithm) -> Result<()> { with_device(ctx, |ctx, device| { let mut device = authenticate_admin(ctx, device)?; match algorithm { - arg_defs::OtpAlgorithm::Hotp => device.erase_hotp_slot(slot), - arg_defs::OtpAlgorithm::Totp => device.erase_totp_slot(slot), + args::OtpAlgorithm::Hotp => device.erase_hotp_slot(slot), + args::OtpAlgorithm::Totp => device.erase_totp_slot(slot), } .map_err(|err| get_error("Could not clear OTP slot", err))?; Ok(()) @@ -741,15 +741,15 @@ pub fn otp_clear(ctx: &mut ExecCtx<'_>, slot: u8, algorithm: arg_defs::OtpAlgori fn print_otp_status( ctx: &mut ExecCtx<'_>, - algorithm: arg_defs::OtpAlgorithm, + algorithm: args::OtpAlgorithm, device: &nitrokey::DeviceWrapper<'_>, all: bool, ) -> Result<()> { let mut slot: u8 = 0; loop { let result = match algorithm { - arg_defs::OtpAlgorithm::Hotp => device.get_hotp_slot_name(slot), - arg_defs::OtpAlgorithm::Totp => device.get_totp_slot_name(slot), + 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, @@ -777,8 +777,8 @@ fn print_otp_status( pub fn otp_status(ctx: &mut ExecCtx<'_>, all: bool) -> Result<()> { with_device(ctx, |ctx, device| { println!(ctx, "alg\tslot\tname")?; - print_otp_status(ctx, arg_defs::OtpAlgorithm::Hotp, &device, all)?; - print_otp_status(ctx, arg_defs::OtpAlgorithm::Totp, &device, all)?; + print_otp_status(ctx, args::OtpAlgorithm::Hotp, &device, all)?; + print_otp_status(ctx, args::OtpAlgorithm::Totp, &device, all)?; Ok(()) }) } @@ -786,11 +786,8 @@ pub fn otp_status(ctx: &mut ExecCtx<'_>, all: bool) -> Result<()> { /// Clear the PIN stored by various operations. pub fn pin_clear(ctx: &mut ExecCtx<'_>) -> Result<()> { with_device(ctx, |_ctx, device| { - pinentry::clear(&pinentry::PinEntry::from( - arg_defs::PinType::Admin, - &device, - )?)?; - pinentry::clear(&pinentry::PinEntry::from(arg_defs::PinType::User, &device)?)?; + pinentry::clear(&pinentry::PinEntry::from(args::PinType::Admin, &device)?)?; + pinentry::clear(&pinentry::PinEntry::from(args::PinType::User, &device)?)?; Ok(()) }) } @@ -801,14 +798,14 @@ pub fn pin_clear(ctx: &mut ExecCtx<'_>) -> Result<()> { /// given PIN type, it will be used. fn choose_pin(ctx: &mut ExecCtx<'_>, pin_entry: &pinentry::PinEntry, new: bool) -> Result { let new_pin = match pin_entry.pin_type() { - arg_defs::PinType::Admin => { + args::PinType::Admin => { if new { &ctx.new_admin_pin } else { &ctx.admin_pin } } - arg_defs::PinType::User => { + args::PinType::User => { if new { &ctx.new_user_pin } else { @@ -828,7 +825,7 @@ fn choose_pin(ctx: &mut ExecCtx<'_>, pin_entry: &pinentry::PinEntry, new: bool) } /// Change a PIN. -pub fn pin_set(ctx: &mut ExecCtx<'_>, pin_type: arg_defs::PinType) -> Result<()> { +pub fn pin_set(ctx: &mut ExecCtx<'_>, pin_type: args::PinType) -> Result<()> { with_device(ctx, |ctx, mut device| { let pin_entry = pinentry::PinEntry::from(pin_type, &device)?; let new_pin = choose_pin(ctx, &pin_entry, true)?; @@ -838,8 +835,8 @@ pub fn pin_set(ctx: &mut ExecCtx<'_>, pin_type: arg_defs::PinType) -> Result<()> &pin_entry, "Could not change the PIN", |current_pin| match pin_type { - arg_defs::PinType::Admin => device.change_admin_pin(¤t_pin, &new_pin), - arg_defs::PinType::User => device.change_user_pin(¤t_pin, &new_pin), + args::PinType::Admin => device.change_admin_pin(¤t_pin, &new_pin), + args::PinType::User => device.change_user_pin(¤t_pin, &new_pin), }, )?; @@ -853,9 +850,9 @@ pub fn pin_set(ctx: &mut ExecCtx<'_>, pin_type: arg_defs::PinType) -> Result<()> /// Unblock and reset the user PIN. pub fn pin_unblock(ctx: &mut ExecCtx<'_>) -> Result<()> { with_device(ctx, |ctx, mut device| { - let pin_entry = pinentry::PinEntry::from(arg_defs::PinType::User, &device)?; + let pin_entry = pinentry::PinEntry::from(args::PinType::User, &device)?; let user_pin = choose_pin(ctx, &pin_entry, false)?; - let pin_entry = pinentry::PinEntry::from(arg_defs::PinType::Admin, &device)?; + let pin_entry = pinentry::PinEntry::from(args::PinType::Admin, &device)?; try_with_pin( ctx, diff --git a/src/main.rs b/src/main.rs index a2c4f48..27097c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,7 +68,7 @@ mod redefine; #[macro_use] mod arg_util; -mod arg_defs; +mod args; mod commands; mod error; mod pinentry; @@ -116,7 +116,7 @@ where #[allow(missing_debug_implementations)] pub struct ExecCtx<'io> { /// The Nitrokey model to use. - pub model: Option, + pub model: Option, /// See `RunCtx::stdout`. pub stdout: &'io mut dyn io::Write, /// See `RunCtx::stderr`. @@ -147,7 +147,7 @@ impl<'io> Stdio for ExecCtx<'io> { fn handle_arguments(ctx: &mut RunCtx<'_>, args: Vec) -> Result<()> { use structopt::StructOpt; - match arg_defs::Args::from_iter_safe(args.iter()) { + match args::Args::from_iter_safe(args.iter()) { Ok(args) => { let mut ctx = ExecCtx { model: args.model, diff --git a/src/pinentry.rs b/src/pinentry.rs index 47c8844..d1387f4 100644 --- a/src/pinentry.rs +++ b/src/pinentry.rs @@ -23,7 +23,7 @@ use std::io; use std::process; use std::str; -use crate::arg_defs; +use crate::args; use crate::error::Error; use crate::ExecCtx; @@ -43,13 +43,13 @@ pub trait SecretEntry: fmt::Debug { #[derive(Debug)] pub struct PinEntry { - pin_type: arg_defs::PinType, + pin_type: args::PinType, model: nitrokey::Model, serial: nitrokey::SerialNumber, } impl PinEntry { - pub fn from<'mgr, D>(pin_type: arg_defs::PinType, device: &D) -> crate::Result + pub fn from<'mgr, D>(pin_type: args::PinType, device: &D) -> crate::Result where D: nitrokey::Device<'mgr>, { @@ -62,7 +62,7 @@ impl PinEntry { }) } - pub fn pin_type(&self) -> arg_defs::PinType { + pub fn pin_type(&self) -> args::PinType { self.pin_type } } @@ -72,16 +72,16 @@ impl SecretEntry for PinEntry { let model = self.model.to_string().to_lowercase(); let suffix = format!("{}:{}", model, self.serial); let cache_id = match self.pin_type { - arg_defs::PinType::Admin => format!("nitrocli:admin:{}", suffix), - arg_defs::PinType::User => format!("nitrocli:user:{}", suffix), + args::PinType::Admin => format!("nitrocli:admin:{}", suffix), + args::PinType::User => format!("nitrocli:user:{}", suffix), }; Some(cache_id.into()) } fn prompt(&self) -> CowStr { match self.pin_type { - arg_defs::PinType::Admin => "Admin PIN", - arg_defs::PinType::User => "User PIN", + args::PinType::Admin => "Admin PIN", + args::PinType::User => "User PIN", } .into() } @@ -90,12 +90,12 @@ impl SecretEntry for PinEntry { format!( "{} for\rNitrokey {} {}", match self.pin_type { - arg_defs::PinType::Admin => match mode { + args::PinType::Admin => match mode { Mode::Choose => "Please enter a new admin PIN", Mode::Confirm => "Please confirm the new admin PIN", Mode::Query => "Please enter the admin PIN", }, - arg_defs::PinType::User => match mode { + args::PinType::User => match mode { Mode::Choose => "Please enter a new user PIN", Mode::Confirm => "Please confirm the new user PIN", Mode::Query => "Please enter the user PIN", @@ -109,8 +109,8 @@ impl SecretEntry for PinEntry { fn min_len(&self) -> u8 { match self.pin_type { - arg_defs::PinType::Admin => 8, - arg_defs::PinType::User => 6, + args::PinType::Admin => 8, + args::PinType::User => 6, } } } diff --git a/src/tests/otp.rs b/src/tests/otp.rs index 4ce1156..f923170 100644 --- a/src/tests/otp.rs +++ b/src/tests/otp.rs @@ -1,7 +1,7 @@ // otp.rs // ************************************************************************* -// * Copyright (C) 2019 Daniel Mueller (deso@posteo.net) * +// * Copyright (C) 2019-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 * @@ -19,7 +19,7 @@ use super::*; -use crate::arg_defs; +use crate::args; #[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 = [ - (arg_defs::OtpSecretFormat::Hex, "123"), - (arg_defs::OtpSecretFormat::Base32, "FBILDWWGA2"), + (args::OtpSecretFormat::Hex, "123"), + (args::OtpSecretFormat::Base32, "FBILDWWGA2"), ]; for (format, secret) in &secrets { -- cgit v1.2.3