aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--nitrocli/src/args.rs112
-rw-r--r--nitrocli/src/commands.rs67
2 files changed, 177 insertions, 2 deletions
diff --git a/nitrocli/src/args.rs b/nitrocli/src/args.rs
index f4035e6..c27fbc2 100644
--- a/nitrocli/src/args.rs
+++ b/nitrocli/src/args.rs
@@ -84,12 +84,14 @@ impl str::FromStr for Command {
#[derive(Debug)]
enum OtpCommand {
Get,
+ Set,
}
impl OtpCommand {
fn execute(&self, args: Vec<String>) -> Result<()> {
match *self {
OtpCommand::Get => otp_get(args),
+ OtpCommand::Set => otp_set(args),
}
}
}
@@ -101,6 +103,7 @@ impl fmt::Display for OtpCommand {
"{}",
match *self {
OtpCommand::Get => "get",
+ OtpCommand::Set => "set",
}
)
}
@@ -112,6 +115,7 @@ impl str::FromStr for OtpCommand {
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"get" => Ok(OtpCommand::Get),
+ "set" => Ok(OtpCommand::Set),
_ => Err(()),
}
}
@@ -148,6 +152,46 @@ impl str::FromStr for OtpAlgorithm {
}
}
+#[derive(Clone, Copy, Debug)]
+enum OtpMode {
+ SixDigits,
+ EightDigits,
+}
+
+impl fmt::Display for OtpMode {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "{}",
+ match *self {
+ OtpMode::SixDigits => "6",
+ OtpMode::EightDigits => "8",
+ }
+ )
+ }
+}
+
+impl str::FromStr for OtpMode {
+ type Err = ();
+
+ fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+ match s {
+ "6" => Ok(OtpMode::SixDigits),
+ "8" => Ok(OtpMode::EightDigits),
+ _ => Err(()),
+ }
+ }
+}
+
+impl From<OtpMode> for nitrokey::OtpMode {
+ fn from(mode: OtpMode) -> Self {
+ match mode {
+ OtpMode::SixDigits => nitrokey::OtpMode::SixDigits,
+ OtpMode::EightDigits => nitrokey::OtpMode::EightDigits,
+ }
+ }
+}
+
fn parse(parser: &argparse::ArgumentParser<'_>, args: Vec<String>) -> Result<()> {
if let Err(err) = parser.parse(args, &mut io::stdout(), &mut io::stderr()) {
Err(Error::ArgparseError(err))
@@ -201,7 +245,7 @@ fn otp(args: Vec<String>) -> Result<()> {
let _ = parser.refer(&mut subcommand).required().add_argument(
"subcommand",
argparse::Store,
- "The subcommand to execute (get)",
+ "The subcommand to execute (get|set)",
);
let _ = parser.refer(&mut subargs).add_argument(
"arguments",
@@ -238,6 +282,72 @@ fn otp_get(args: Vec<String>) -> Result<()> {
commands::otp_get(slot, algorithm)
}
+/// Configure a one-time password slot on the Nitrokey device.
+pub fn otp_set(args: Vec<String>) -> Result<()> {
+ let mut slot: u8 = 0;
+ let mut algorithm = OtpAlgorithm::Totp;
+ let mut name = "".to_owned();
+ let mut secret = "".to_owned();
+ let mut digits = OtpMode::SixDigits;
+ let mut counter: u64 = 0;
+ let mut time_window: u16 = 30;
+ let mut ascii = false;
+ let mut parser = argparse::ArgumentParser::new();
+ parser.set_description("Configures a one-time password slot");
+ 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 or totp, default: totp",
+ );
+ let _ = parser.refer(&mut name).required().add_argument(
+ "name",
+ argparse::Store,
+ "The name of the slot",
+ );
+ let _ = parser.refer(&mut secret).required().add_argument(
+ "secret",
+ argparse::Store,
+ "The secret to store on the slot as a hexadecimal string (unless --ascii is set)",
+ );
+ let _ = parser.refer(&mut digits).add_option(
+ &["-d", "--digits"],
+ argparse::Store,
+ "The number of digits to use for the one-time password (6 or 8, default: 6)",
+ );
+ let _ = parser.refer(&mut counter).add_option(
+ &["-c", "--counter"],
+ argparse::Store,
+ "The counter value for HOTP (default: 0)",
+ );
+ let _ = parser.refer(&mut time_window).add_option(
+ &["-t", "--time-window"],
+ argparse::Store,
+ "The time window for TOTP (default: 30)",
+ );
+ let _ = parser.refer(&mut ascii).add_option(
+ &["--ascii"],
+ argparse::StoreTrue,
+ "Interpret the given secret as an ASCII string of the secret",
+ );
+ parse(&parser, args)?;
+ drop(parser);
+
+ let data = nitrokey::OtpSlotData {
+ number: slot,
+ name,
+ secret,
+ mode: nitrokey::OtpMode::from(digits),
+ use_enter: false,
+ token_id: None,
+ };
+ commands::otp_set(data, algorithm, counter, time_window, ascii)
+}
+
/// Parse the command-line arguments and return the selected command and
/// the remaining arguments for the command.
fn parse_arguments(args: Vec<String>) -> Result<(Command, Vec<String>)> {
diff --git a/nitrocli/src/commands.rs b/nitrocli/src/commands.rs
index a82734e..8dc8c42 100644
--- a/nitrocli/src/commands.rs
+++ b/nitrocli/src/commands.rs
@@ -19,6 +19,7 @@
use std::result;
+use nitrokey::ConfigureOtp;
use nitrokey::Device;
use nitrokey::GenerateOtp;
@@ -73,7 +74,6 @@ where
}
/// Authenticate the given device with the admin PIN.
-#[allow(unused)]
fn authenticate_admin<T>(device: T) -> Result<nitrokey::Admin<T>>
where
T: Device,
@@ -276,3 +276,68 @@ pub fn otp_get(slot: u8, algorithm: args::OtpAlgorithm) -> Result<()> {
println!("{}", otp);
Ok(())
}
+
+/// Prepare an ASCII secret string for libnitrokey.
+///
+/// libnitrokey expects secrets as hexadecimal strings. This function transforms an ASCII string
+/// into a hexadecimal string or returns an error if the given string contains non-ASCII
+/// characters.
+fn prepare_secret(secret: &str) -> Result<String> {
+ if secret.is_ascii() {
+ Ok(
+ secret
+ .as_bytes()
+ .iter()
+ .map(|c| format!("{:x}", c))
+ .collect::<Vec<String>>()
+ .join(""),
+ )
+ } else {
+ Err(Error::Error(
+ "The given secret is not an ASCII string despite --ascii being set".to_string(),
+ ))
+ }
+}
+
+/// Configure a one-time password slot on the Nitrokey device.
+pub fn otp_set(
+ data: nitrokey::OtpSlotData,
+ algorithm: args::OtpAlgorithm,
+ counter: u64,
+ time_window: u16,
+ ascii: bool,
+) -> Result<()> {
+ let secret = if ascii {
+ prepare_secret(&data.secret)?
+ } else {
+ data.secret
+ };
+ let data = nitrokey::OtpSlotData { secret, ..data };
+ let device = authenticate_admin(get_device()?)?;
+ match algorithm {
+ args::OtpAlgorithm::Hotp => device.write_hotp_slot(data, counter),
+ args::OtpAlgorithm::Totp => device.write_totp_slot(data, time_window),
+ }
+ .map_err(|err| get_error("Could not write OTP slot", &err))?;
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn prepare_secret_ascii() {
+ let result = prepare_secret("12345678901234567890");
+ assert_eq!(
+ "3132333435363738393031323334353637383930".to_string(),
+ result.unwrap()
+ );
+ }
+
+ #[test]
+ fn prepare_secret_non_ascii() {
+ let result = prepare_secret("Österreich");
+ assert!(result.is_err());
+ }
+}