diff options
Diffstat (limited to 'src/args.rs')
-rw-r--r-- | src/args.rs | 433 |
1 files changed, 433 insertions, 0 deletions
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 <http://www.gnu.org/licenses/>. * +// ************************************************************************* + +/// 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| 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<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, String> { + 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<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 => 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<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 => 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", +]} |