From 86b3170b1dd4c1955e540f5c914a317302754be1 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 23 Dec 2018 02:05:32 +0100 Subject: Implement the otp get subcommand This patch implements the `otp get` subcommand that allows the user to generate a one-time password on the Nitrokey device. Before generating the password, the device configuration is checked so that the user only has to enter a PIN if it is required for the OTP generation. --- nitrocli/src/args.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++++-- nitrocli/src/commands.rs | 32 ++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 3 deletions(-) diff --git a/nitrocli/src/args.rs b/nitrocli/src/args.rs index 2112243..f4035e6 100644 --- a/nitrocli/src/args.rs +++ b/nitrocli/src/args.rs @@ -87,9 +87,9 @@ enum OtpCommand { } impl OtpCommand { - fn execute(&self, _args: Vec) -> Result<()> { + fn execute(&self, args: Vec) -> Result<()> { match *self { - OtpCommand::Get => Err(Error::Error("Not implementend".to_string())), + OtpCommand::Get => otp_get(args), } } } @@ -117,6 +117,37 @@ impl str::FromStr for OtpCommand { } } +#[derive(Clone, Copy, Debug)] +pub enum OtpAlgorithm { + Hotp, + Totp, +} + +impl fmt::Display for OtpAlgorithm { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match *self { + OtpAlgorithm::Hotp => "hotp", + OtpAlgorithm::Totp => "totp", + } + ) + } +} + +impl str::FromStr for OtpAlgorithm { + type Err = (); + + fn from_str(s: &str) -> std::result::Result { + match s { + "hotp" => Ok(OtpAlgorithm::Hotp), + "totp" => Ok(OtpAlgorithm::Totp), + _ => 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)) @@ -185,6 +216,28 @@ fn otp(args: Vec) -> Result<()> { subcommand.execute(subargs) } +/// Generate a one-time password on the Nitrokey device. +fn otp_get(args: Vec) -> Result<()> { + let mut slot: u8 = 0; + let mut algorithm = OtpAlgorithm::Totp; + let mut parser = argparse::ArgumentParser::new(); + parser.set_description("Generates a one-time password"); + let _ = + parser + .refer(&mut slot) + .required() + .add_argument("slot", argparse::Store, "The OTP slot to use"); + let _ = parser.refer(&mut algorithm).add_option( + &["-a", "--algorithm"], + argparse::Store, + "The OTP algorithm to use (hotp|totp)", + ); + parse(&parser, args)?; + drop(parser); + + commands::otp_get(slot, algorithm) +} + /// 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)> { diff --git a/nitrocli/src/commands.rs b/nitrocli/src/commands.rs index 9761728..a82734e 100644 --- a/nitrocli/src/commands.rs +++ b/nitrocli/src/commands.rs @@ -20,7 +20,9 @@ use std::result; use nitrokey::Device; +use nitrokey::GenerateOtp; +use crate::args; use crate::error::Error; use crate::pinentry; use crate::Result; @@ -30,6 +32,11 @@ fn get_error(msg: &str, err: &nitrokey::CommandError) -> Error { Error::Error(format!("{}: {:?}", msg, err)) } +/// Connect to any Nitrokey device and return it. +fn get_device() -> Result { + nitrokey::connect().map_err(|_| Error::Error("Nitrokey device not found".to_string())) +} + /// Connect to a Nitrokey Storage device and return it. fn get_storage_device() -> Result { nitrokey::Storage::connect() @@ -53,7 +60,6 @@ where } /// Authenticate the given device with the user PIN. -#[allow(unused)] fn authenticate_user(device: T) -> Result> where T: Device, @@ -246,3 +252,27 @@ pub fn clear() -> Result<()> { pinentry::clear_passphrase(pinentry::PinType::User)?; Ok(()) } + +fn get_otp(slot: u8, algorithm: args::OtpAlgorithm, device: &T) -> Result { + match algorithm { + 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)) +} + +/// Generate a one-time password on the Nitrokey device. +pub fn otp_get(slot: u8, algorithm: args::OtpAlgorithm) -> Result<()> { + let device = get_device()?; + let config = device + .get_config() + .map_err(|err| get_error("Could not get device configuration", &err))?; + let otp = if config.user_password { + let user = authenticate_user(device)?; + get_otp(slot, algorithm, &user) + } else { + get_otp(slot, algorithm, &device) + }?; + println!("{}", otp); + Ok(()) +} -- cgit v1.2.1