aboutsummaryrefslogtreecommitdiff
path: root/nitrocli/src/commands.rs
diff options
context:
space:
mode:
authorRobin Krahl <robin.krahl@ireas.org>2018-12-23 02:08:47 +0100
committerDaniel Mueller <deso@posteo.net>2018-12-24 18:15:28 -0800
commit1630c7872631f5f7e5bab599121df1fed26e47da (patch)
treed831a9029f970731534cfdef9c05bb9196fe096a /nitrocli/src/commands.rs
parent86b3170b1dd4c1955e540f5c914a317302754be1 (diff)
downloadnitrocli-1630c7872631f5f7e5bab599121df1fed26e47da.tar.gz
nitrocli-1630c7872631f5f7e5bab599121df1fed26e47da.tar.bz2
Implement the otp set subcommand
This patch implements the `otp set` subcommand that configures an OTP slot. There are two ways to specify an OTP secret: as a hexadecimal string (that means that every two characters are interpreted as a hexadecimal representation of one byte of the secret) or as an ASCII string (that means that the ASCII code of every character is interpreted as one byte of the secret). As the HOTP RFC mentions both representations, this implementation supports both.
Diffstat (limited to 'nitrocli/src/commands.rs')
-rw-r--r--nitrocli/src/commands.rs67
1 files changed, 66 insertions, 1 deletions
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());
+ }
+}