summaryrefslogtreecommitdiff
path: root/src/otp.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/otp.rs')
-rw-r--r--src/otp.rs350
1 files changed, 350 insertions, 0 deletions
diff --git a/src/otp.rs b/src/otp.rs
new file mode 100644
index 0000000..c0a470f
--- /dev/null
+++ b/src/otp.rs
@@ -0,0 +1,350 @@
+use nitrokey_sys;
+use std::ffi::CString;
+use util::{result_from_string, CommandError, CommandStatus};
+
+/// Modes for one-time password generation.
+#[derive(Debug, PartialEq)]
+pub enum OtpMode {
+ /// Generate one-time passwords with six digits.
+ SixDigits,
+ /// Generate one-time passwords with eight digits.
+ EightDigits,
+}
+
+/// Provides methods to configure and erase OTP slots on a Nitrokey device.
+pub trait ConfigureOtp {
+ /// Configure an HOTP slot with the given data and set the HOTP counter to
+ /// the given value (default 0).
+ ///
+ /// # Errors
+ ///
+ /// - [`InvalidSlot`][] if there is no slot with the given number
+ /// - [`InvalidString`][] if the provided token ID contains a null byte
+ /// - [`NoName`][] if the provided name is empty
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::{CommandStatus, ConfigureOtp, OtpMode, OtpSlotData};
+ /// # use nitrokey::CommandError;
+ ///
+ /// # fn try_main() -> Result<(), (CommandError)> {
+ /// let device = nitrokey::connect()?;
+ /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits);
+ /// match device.authenticate_admin("12345678") {
+ /// Ok(admin) => {
+ /// match admin.write_hotp_slot(slot_data, 0) {
+ /// CommandStatus::Success => println!("Successfully wrote slot."),
+ /// CommandStatus::Error(err) => println!("Could not write slot: {:?}", err),
+ /// }
+ /// },
+ /// Err((_, err)) => println!("Could not authenticate as admin: {:?}", err),
+ /// }
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
+ /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
+ /// [`NoName`]: enum.CommandError.html#variant.NoName
+ fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> CommandStatus;
+
+ /// Configure a TOTP slot with the given data and set the TOTP time window
+ /// to the given value (default 30).
+ ///
+ /// # Errors
+ ///
+ /// - [`InvalidSlot`][] if there is no slot with the given number
+ /// - [`InvalidString`][] if the provided token ID contains a null byte
+ /// - [`NoName`][] if the provided name is empty
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::{CommandStatus, ConfigureOtp, OtpMode, OtpSlotData};
+ /// # use nitrokey::CommandError;
+ ///
+ /// # fn try_main() -> Result<(), (CommandError)> {
+ /// let device = nitrokey::connect()?;
+ /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::EightDigits);
+ /// match device.authenticate_admin("12345678") {
+ /// Ok(admin) => {
+ /// match admin.write_totp_slot(slot_data, 30) {
+ /// CommandStatus::Success => println!("Successfully wrote slot."),
+ /// CommandStatus::Error(err) => println!("Could not write slot: {:?}", err),
+ /// }
+ /// },
+ /// Err((_, err)) => println!("Could not authenticate as admin: {:?}", err),
+ /// }
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
+ /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
+ /// [`NoName`]: enum.CommandError.html#variant.NoName
+ fn write_totp_slot(&self, data: OtpSlotData, time_window: u16) -> CommandStatus;
+
+ /// Erases an HOTP slot.
+ ///
+ /// # Errors
+ ///
+ /// - [`InvalidSlot`][] if there is no slot with the given number
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::{CommandStatus, ConfigureOtp};
+ /// # use nitrokey::CommandError;
+ ///
+ /// # fn try_main() -> Result<(), (CommandError)> {
+ /// let device = nitrokey::connect()?;
+ /// match device.authenticate_admin("12345678") {
+ /// Ok(admin) => {
+ /// match admin.erase_hotp_slot(1) {
+ /// CommandStatus::Success => println!("Successfully erased slot."),
+ /// CommandStatus::Error(err) => println!("Could not erase slot: {:?}", err),
+ /// }
+ /// },
+ /// Err((_, err)) => println!("Could not authenticate as admin: {:?}", err),
+ /// }
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
+ fn erase_hotp_slot(&self, slot: u8) -> CommandStatus;
+
+ /// Erases a TOTP slot.
+ ///
+ /// # Errors
+ ///
+ /// - [`InvalidSlot`][] if there is no slot with the given number
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::{CommandStatus, ConfigureOtp};
+ /// # use nitrokey::CommandError;
+ ///
+ /// # fn try_main() -> Result<(), (CommandError)> {
+ /// let device = nitrokey::connect()?;
+ /// match device.authenticate_admin("12345678") {
+ /// Ok(admin) => {
+ /// match admin.erase_totp_slot(1) {
+ /// CommandStatus::Success => println!("Successfully erased slot."),
+ /// CommandStatus::Error(err) => println!("Could not erase slot: {:?}", err),
+ /// }
+ /// },
+ /// Err((_, err)) => println!("Could not authenticate as admin: {:?}", err),
+ /// }
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
+ fn erase_totp_slot(&self, slot: u8) -> CommandStatus;
+}
+
+/// Provides methods to generate OTP codes and to query OTP slots on a Nitrokey
+/// device.
+pub trait GenerateOtp {
+ /// Returns the name of the given HOTP slot.
+ ///
+ /// # Errors
+ ///
+ /// - [`InvalidSlot`][] if there is no slot with the given number
+ /// - [`SlotNotProgrammed`][] if the given slot is not configured
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::{CommandError, GenerateOtp};
+ ///
+ /// # fn try_main() -> Result<(), CommandError> {
+ /// let device = nitrokey::connect()?;
+ /// match device.get_hotp_slot_name(1) {
+ /// Ok(name) => println!("HOTP slot 1: {:?}", name),
+ /// Err(CommandError::SlotNotProgrammed) => println!("HOTP slot 1 not programmed"),
+ /// Err(err) => println!("Could not get slot name: {:?}", err),
+ /// };
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
+ /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
+ fn get_hotp_slot_name(&self, slot: u8) -> Result<String, CommandError> {
+ unsafe { result_from_string(nitrokey_sys::NK_get_hotp_slot_name(slot)) }
+ }
+
+ /// Returns the name of the given TOTP slot.
+ ///
+ /// # Errors
+ ///
+ /// - [`InvalidSlot`][] if there is no slot with the given number
+ /// - [`SlotNotProgrammed`][] if the given slot is not configured
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::{CommandError, GenerateOtp};
+ ///
+ /// # fn try_main() -> Result<(), CommandError> {
+ /// let device = nitrokey::connect()?;
+ /// match device.get_totp_slot_name(1) {
+ /// Ok(name) => println!("TOTP slot 1: {:?}", name),
+ /// Err(CommandError::SlotNotProgrammed) => println!("TOTP slot 1 not programmed"),
+ /// Err(err) => println!("Could not get slot name: {:?}", err),
+ /// };
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
+ /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
+ fn get_totp_slot_name(&self, slot: u8) -> Result<String, CommandError> {
+ unsafe { result_from_string(nitrokey_sys::NK_get_totp_slot_name(slot)) }
+ }
+
+ /// Generates an HOTP code on the given slot. This operation may require
+ /// user authorization, depending on the device configuration (see
+ /// [`get_config`][]).
+ ///
+ /// # Errors
+ ///
+ /// - [`InvalidSlot`][] if there is no slot with the given number
+ /// - [`NotAuthorized`][] if OTP generation requires user authentication
+ /// - [`SlotNotProgrammed`][] if the given slot is not configured
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::GenerateOtp;
+ /// # use nitrokey::CommandError;
+ ///
+ /// # fn try_main() -> Result<(), CommandError> {
+ /// let device = nitrokey::connect()?;
+ /// let code = device.get_hotp_code(1)?;
+ /// println!("Generated HOTP code on slot 1: {:?}", code);
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`get_config`]: trait.Device.html#method.get_config
+ /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
+ /// [`NotAuthorized`]: enum.CommandError.html#variant.NotAuthorized
+ /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
+ fn get_hotp_code(&self, slot: u8) -> Result<String, CommandError> {
+ unsafe {
+ return result_from_string(nitrokey_sys::NK_get_hotp_code(slot));
+ }
+ }
+
+ /// Generates a TOTP code on the given slot. This operation may require
+ /// user authorization, depending on the device configuration (see
+ /// [`get_config`][]).
+ ///
+ /// To make sure that the Nitrokey’s time is in sync, consider calling
+ /// [`set_time`][] before calling this method.
+ ///
+ /// # Errors
+ ///
+ /// - [`InvalidSlot`][] if there is no slot with the given number
+ /// - [`NotAuthorized`][] if OTP generation requires user authentication
+ /// - [`SlotNotProgrammed`][] if the given slot is not configured
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::GenerateOtp;
+ /// # use nitrokey::CommandError;
+ ///
+ /// # fn try_main() -> Result<(), CommandError> {
+ /// let device = nitrokey::connect()?;
+ /// let code = device.get_totp_code(1)?;
+ /// println!("Generated TOTP code on slot 1: {:?}", code);
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`set_time`]: trait.Device.html#method.set_time
+ /// [`get_config`]: trait.Device.html#method.get_config
+ /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot
+ /// [`NotAuthorized`]: enum.CommandError.html#variant.NotAuthorized
+ /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
+ fn get_totp_code(&self, slot: u8) -> Result<String, CommandError> {
+ unsafe {
+ return result_from_string(nitrokey_sys::NK_get_totp_code(slot, 0, 0, 0));
+ }
+ }
+}
+
+/// The configuration for an OTP slot.
+#[derive(Debug)]
+pub struct OtpSlotData {
+ /// The number of the slot – must be less than three for HOTP and less than
+ /// 15 for TOTP.
+ pub number: u8,
+ /// The name of the slot – must not be empty.
+ pub name: String,
+ /// The secret for the slot.
+ pub secret: String,
+ /// The OTP generation mode.
+ pub mode: OtpMode,
+ /// If true, press the enter key after sending an OTP code using double-pressed
+ /// numlock, capslock or scrolllock.
+ pub use_enter: bool,
+ /// Set the token ID [OATH Token Identifier Specification][tokspec], section
+ /// “Class A”.
+ ///
+ /// [tokspec]: https://openauthentication.org/token-specs/
+ pub token_id: Option<String>,
+}
+
+#[derive(Debug)]
+pub struct RawOtpSlotData {
+ pub number: u8,
+ pub name: CString,
+ pub secret: CString,
+ pub mode: OtpMode,
+ pub use_enter: bool,
+ pub use_token_id: bool,
+ pub token_id: CString,
+}
+
+impl OtpSlotData {
+ /// Constructs a new instance of this struct.
+ pub fn new(number: u8, name: &str, secret: &str, mode: OtpMode) -> OtpSlotData {
+ OtpSlotData {
+ number,
+ name: String::from(name),
+ secret: String::from(secret),
+ mode,
+ use_enter: false,
+ token_id: None,
+ }
+ }
+}
+
+impl RawOtpSlotData {
+ pub fn new(data: OtpSlotData) -> Result<RawOtpSlotData, CommandError> {
+ let name = CString::new(data.name);
+ let secret = CString::new(data.secret);
+ let use_token_id = data.token_id.is_some();
+ let token_id = CString::new(data.token_id.unwrap_or_else(String::new));
+ if name.is_err() || secret.is_err() || token_id.is_err() {
+ return Err(CommandError::InvalidString);
+ }
+
+ Ok(RawOtpSlotData {
+ number: data.number,
+ name: name.unwrap(),
+ secret: secret.unwrap(),
+ mode: data.mode,
+ use_enter: data.use_enter,
+ use_token_id,
+ token_id: token_id.unwrap(),
+ })
+ }
+}