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::{Authenticate, 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::{Authenticate, 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::{Authenticate, 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::{Authenticate, 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 { /// Sets the time on the Nitrokey. This command may set the time to arbitrary values. `time` /// is the number of seconds since January 1st, 1970 (Unix timestamp). /// /// The time is used for TOTP generation (see [`get_totp_code`][]). /// /// # Example /// /// ```ignore /// extern crate chrono; /// /// use chrono::Utc; /// use nitrokey::Device; /// # use nitrokey::CommandError; /// /// # fn try_main() -> Result<(), CommandError> { /// let device = nitrokey::connect()?; /// let time = Utc::now().timestamp(); /// if time < 0 { /// println!("Timestamps before 1970-01-01 are not supported!"); /// } else { /// device.set_time(time as u64); /// } /// # Ok(()) /// # } /// ``` /// /// # Errors /// /// - [`Timestamp`][] if the time could not be set /// /// [`get_totp_code`]: #method.get_totp_code /// [`Timestamp`]: enum.CommandError.html#variant.Timestamp fn set_time(&self, time: u64) -> CommandStatus { unsafe { CommandStatus::from(nitrokey_sys::NK_totp_set_time(time)) } } /// 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 { 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 { 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 { 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 /// /// ```ignore /// extern crate chrono; /// /// use nitrokey::GenerateOtp; /// # use nitrokey::CommandError; /// /// # fn try_main() -> Result<(), CommandError> { /// let device = nitrokey::connect()?; /// let time = Utc::now().timestamp(); /// if time < 0 { /// println!("Timestamps before 1970-01-01 are not supported!"); /// } else { /// device.set_time(time as u64); /// let code = device.get_totp_code(1)?; /// println!("Generated TOTP code on slot 1: {:?}", code); /// } /// # Ok(()) /// # } /// ``` /// /// [`set_time`]: #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 { 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, see [OATH Token Identifier Specification][tokspec], section “Class A”. /// /// [tokspec]: https://openauthentication.org/token-specs/ pub token_id: Option, } #[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, T: Into>( number: u8, name: S, secret: T, mode: OtpMode, ) -> OtpSlotData { OtpSlotData { number, name: name.into(), secret: secret.into(), mode, use_enter: false, token_id: None, } } /// Enables pressing the enter key after sending an OTP code using double-pressed numlock, /// capslock or scrollock. pub fn use_enter(mut self) -> OtpSlotData { self.use_enter = true; self } /// Sets the token ID, see [OATH Token Identifier Specification][tokspec], section “Class A”. /// /// [tokspec]: https://openauthentication.org/token-specs/ pub fn token_id>(mut self, id: S) -> OtpSlotData { self.token_id = Some(id.into()); self } } impl RawOtpSlotData { pub fn new(data: OtpSlotData) -> Result { 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(), }) } }