From 8a59f307a2e0b9fa398ac200da44d8e5725150a7 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 31 Dec 2018 18:10:40 +0000 Subject: Implement the pin command and rename clear to pin clear We have functionality for changing the Nitrokey's user & admin PINs as well as for resetting the user PIN coming up. With the prospect of this new functionality arriving, it makes sense to introduce a new top-level command for the sole purpose of PIN management. This change introduces such a command, pin, and moves the existing clear command for clearing the PIN cache into it. --- nitrocli/CHANGELOG.md | 2 ++ nitrocli/README.md | 3 +- nitrocli/doc/nitrocli.1 | 13 +++++-- nitrocli/src/args.rs | 88 ++++++++++++++++++++++++++++++++++++++++-------- nitrocli/src/commands.rs | 14 ++++---- 5 files changed, 96 insertions(+), 24 deletions(-) diff --git a/nitrocli/CHANGELOG.md b/nitrocli/CHANGELOG.md index ae6b422..f36a859 100644 --- a/nitrocli/CHANGELOG.md +++ b/nitrocli/CHANGELOG.md @@ -8,6 +8,8 @@ Unreleased - Removed the `hid`, `hidapi-sys` and `pkg-config` dependencies - Added the `otp` command for working with one-time passwords - Added the `config` command for reading and writing the device configuration +- Added the `pin` command for managing PINs + - Renamed the `clear` command to `pin clear` - Moved `open` and `close` commands as subcommands into newly introduced `storage` command - Moved printing of storage related information from `status` command diff --git a/nitrocli/README.md b/nitrocli/README.md index c36caff..68e4da6 100644 --- a/nitrocli/README.md +++ b/nitrocli/README.md @@ -12,7 +12,6 @@ certain commands on the [Nitrokey Storage][nitrokey-storage] device. The following commands are currently supported: - status: Report status information about the Nitrokey. -- clear: Remove the user and admin PIN from gpg-agent's cache. - config: Access the Nitrokey's configuration - get: Read the current configuration. - set: Change the configuration. @@ -25,6 +24,8 @@ The following commands are currently supported: - set: Set an OTP slot. - status: List all OTP slots. - clear: Delete an OTP slot. +- pin: Manage the Nitrokey's PINs. + - clear: Remove the user and admin PIN from gpg-agent's cache. ### *Note:* ---------------------------------------------------------------------- diff --git a/nitrocli/doc/nitrocli.1 b/nitrocli/doc/nitrocli.1 index 21aab03..ef56b22 100644 --- a/nitrocli/doc/nitrocli.1 +++ b/nitrocli/doc/nitrocli.1 @@ -16,8 +16,6 @@ It can be used to access the encrypted volume and the one-time password generato Print the status of the connected Nitrokey device, including the stick serial number, the firmware version, and the PIN retry count. .TP -.B nitrocli clear -Clear the passphrases cached by the other commands. .SS Storage .TP @@ -120,6 +118,17 @@ passwords using the \fBotp get\fR command. If \fB\-\-no\-otp\-pin\fR is set, OTP generation can be performed without PIN. These two options are mutually exclusive. +.SS PINs +Nitrokey devices have two PINs: the user PIN and the admin PIN. The user +PIN must have at least six, the admin PIN at least eight characters. The +user PIN is required for commands such as \fBotp get\fR (depending on +the configuration) and for all \fBpws\fR commands. +The admin PIN is usually required to change the device configuration. + +.TP +.B nitrocli pin clear +Clear the PINs cached by the other commands. + .SH EXAMPLES .SS One-time passwords Configure a one-time password slot with a hexadecimal secret representation: diff --git a/nitrocli/src/args.rs b/nitrocli/src/args.rs index b4733f6..edfd811 100644 --- a/nitrocli/src/args.rs +++ b/nitrocli/src/args.rs @@ -30,9 +30,9 @@ type Result = result::Result; /// A top-level command for nitrocli. #[derive(Debug)] pub enum Command { - Clear, Config, Otp, + Pin, Status, Storage, } @@ -41,9 +41,9 @@ impl Command { /// Execute this command with the given arguments. pub fn execute(&self, args: Vec) -> Result<()> { match *self { - Command::Clear => clear(args), Command::Config => config(args), Command::Otp => otp(args), + Command::Pin => pin(args), Command::Status => status(args), Command::Storage => storage(args), } @@ -56,9 +56,9 @@ impl fmt::Display for Command { f, "{}", match *self { - Command::Clear => "clear", Command::Config => "config", Command::Otp => "otp", + Command::Pin => "pin", Command::Status => "status", Command::Storage => "storage", } @@ -71,9 +71,9 @@ impl str::FromStr for Command { fn from_str(s: &str) -> result::Result { match s { - "clear" => Ok(Command::Clear), "config" => Ok(Command::Config), "otp" => Ok(Command::Otp), + "pin" => Ok(Command::Pin), "status" => Ok(Command::Status), "storage" => Ok(Command::Storage), _ => Err(()), @@ -275,6 +275,42 @@ impl From for nitrokey::OtpMode { } } +#[derive(Debug)] +enum PinCommand { + Clear, +} + +impl PinCommand { + fn execute(&self, args: Vec) -> Result<()> { + match *self { + PinCommand::Clear => pin_clear(args), + } + } +} + +impl fmt::Display for PinCommand { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match *self { + PinCommand::Clear => "clear", + } + ) + } +} + +impl str::FromStr for PinCommand { + type Err = (); + + fn from_str(s: &str) -> result::Result { + match s { + "clear" => Ok(PinCommand::Clear), + _ => Err(()), + } + } +} + fn parse(parser: &argparse::ArgumentParser<'_>, args: Vec) -> Result<()> { if let Err(err) = parser.parse(args, &mut io::stdout(), &mut io::stderr()) { Err(Error::ArgparseError(err)) @@ -387,15 +423,6 @@ fn storage_status(args: Vec) -> Result<()> { commands::storage_status() } -/// Clear the PIN as cached by various other commands. -fn clear(args: Vec) -> Result<()> { - let mut parser = argparse::ArgumentParser::new(); - parser.set_description("Clears the cached passphrases"); - parse(&parser, args)?; - - commands::clear() -} - /// Execute a config subcommand. fn config(args: Vec) -> Result<()> { let mut subcommand = ConfigCommand::Get; @@ -653,6 +680,39 @@ fn otp_status(args: Vec) -> Result<()> { commands::otp_status(all) } +/// Execute a PIN subcommand. +fn pin(args: Vec) -> Result<()> { + let mut subcommand = PinCommand::Clear; + let mut subargs = vec![]; + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Manages the Nitrokey PINs"); + let _ = parser.refer(&mut subcommand).required().add_argument( + "subcommand", + argparse::Store, + "The subcommand to execute (clear)", + ); + let _ = parser.refer(&mut subargs).add_argument( + "arguments", + argparse::List, + "The arguments for the subcommand", + ); + parser.stop_on_first_argument(true); + parse(&parser, args)?; + drop(parser); + + subargs.insert(0, format!("nitrocli pin {}", subcommand)); + subcommand.execute(subargs) +} + +/// Clear the PIN as cached by various other commands. +fn pin_clear(args: Vec) -> Result<()> { + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Clears the cached PINs"); + parse(&parser, args)?; + + commands::pin_clear() +} + /// Parse the command-line arguments and return the selected command and /// the remaining arguments for the command. fn parse_arguments(args: Vec) -> Result<(Command, Vec)> { @@ -663,7 +723,7 @@ fn parse_arguments(args: Vec) -> Result<(Command, Vec)> { let _ = parser.refer(&mut command).required().add_argument( "command", argparse::Store, - "The command to execute (clear|config|otp|status|storage)", + "The command to execute (config|otp|pin|status|storage)", ); let _ = parser.refer(&mut subargs).add_argument( "arguments", diff --git a/nitrocli/src/commands.rs b/nitrocli/src/commands.rs index 17c8c8c..7f25415 100644 --- a/nitrocli/src/commands.rs +++ b/nitrocli/src/commands.rs @@ -275,13 +275,6 @@ pub fn storage_status() -> Result<()> { Ok(()) } -/// Clear the PIN stored when opening the nitrokey's encrypted volume. -pub fn clear() -> Result<()> { - pinentry::clear_passphrase(pinentry::PinType::Admin)?; - pinentry::clear_passphrase(pinentry::PinType::User)?; - Ok(()) -} - /// Return a String representation of the given Option. fn format_option(option: Option) -> String { match option { @@ -474,6 +467,13 @@ pub fn otp_status(all: bool) -> Result<()> { Ok(()) } +/// Clear the PIN stored by various operations. +pub fn pin_clear() -> Result<()> { + pinentry::clear_passphrase(pinentry::PinType::Admin)?; + pinentry::clear_passphrase(pinentry::PinType::User)?; + Ok(()) +} + #[cfg(test)] mod tests { use super::*; -- cgit v1.2.3