diff options
Diffstat (limited to 'src/lib.rs')
-rw-r--r-- | src/lib.rs | 1303 |
1 files changed, 13 insertions, 1290 deletions
@@ -42,7 +42,7 @@ //! Configure an HOTP slot: //! //! ```no_run -//! use nitrokey::{CommandStatus, Device, OtpMode, OtpSlotData}; +//! use nitrokey::{CommandStatus, ConfigureOtp, Device, OtpMode, OtpSlotData}; //! # use nitrokey::CommandError; //! //! # fn try_main() -> Result<(), (CommandError)> { @@ -64,7 +64,7 @@ //! Generate an HOTP one-time password: //! //! ```no_run -//! use nitrokey::Device; +//! use nitrokey::{Device, GenerateOtp}; //! # use nitrokey::CommandError; //! //! # fn try_main() -> Result<(), (CommandError)> { @@ -82,8 +82,8 @@ //! [`connect`]: fn.connect.html //! [`connect_model`]: fn.connect_model.html //! [`device`]: struct.AuthenticatedDevice.html#method.device -//! [`get_hotp_code`]: trait.Device.html#method.get_hotp_code -//! [`get_totp_code`]: trait.Device.html#method.get_totp_code +//! [`get_hotp_code`]: trait.ProvideOtp.html#method.get_hotp_code +//! [`get_totp_code`]: trait.ProvideOtp.html#method.get_totp_code //! [`set_debug`]: fn.set_debug.html //! [`set_log_level`]: fn.set_log_level.html //! [`AdminAuthenticatedDevice`]: struct.AdminAuthenticatedDevice.html @@ -94,611 +94,18 @@ extern crate libc; extern crate nitrokey_sys; extern crate rand; -use std::ffi::CString; -use std::ffi::CStr; -use libc::c_int; -use rand::Rng; - +mod config; +mod device; +mod otp; +mod util; #[cfg(test)] mod tests; -/// 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, -} - -/// Error types returned by Nitrokey device or by the library. -#[derive(Debug, PartialEq)] -pub enum CommandError { - /// A packet with a wrong checksum has been sent or received. - WrongCrc, - /// A command tried to access an OTP slot that does not exist. - WrongSlot, - /// A command tried to generate an OTP on a slot that is not configured. - SlotNotProgrammed, - /// The provided password is wrong. - WrongPassword, - /// You are not authorized for this command or provided a wrong temporary - /// password. - NotAuthorized, - /// An error occured when getting or setting the time. - Timestamp, - /// You did not provide a name for the OTP slot. - NoName, - /// This command is not supported by this device. - NotSupported, - /// This command is unknown. - UnknownCommand, - /// AES decryptionfailed. - AesDecryptionFailed, - /// An unknown error occured. - Unknown, - /// You passed a string containing a null byte. - InvalidString, - /// You passed an invalid slot. - InvalidSlot, - /// An error occured during random number generation. - RngError, -} - -/// Command execution status. -#[derive(Debug, PartialEq)] -pub enum CommandStatus { - /// The command was successful. - Success, - /// An error occured during command execution. - Error(CommandError), -} - -/// Log level for libnitrokey. -#[derive(Debug, PartialEq)] -pub enum LogLevel { - /// Only log error messages. - Error, - /// Log error messages and warnings. - Warning, - /// Log error messages, warnings and info messages. - Info, - /// Log error messages, warnings, info messages and debug messages. - DebugL1, - /// Log error messages, warnings, info messages and detailed debug - /// messages. - Debug, - /// Log error messages, warnings, info messages and very detailed debug - /// messages. - DebugL2, -} - -/// Available Nitrokey models. -#[derive(Debug, PartialEq)] -pub enum Model { - /// The Nitrokey Storage. - Storage, - /// The Nitrokey Pro. - Pro, -} - -/// The configuration for a Nitrokey. -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct Config { - /// If set, the stick will generate a code from the HOTP slot with the - /// given number if numlock is pressed. The slot number must be 0, 1 or 2. - pub numlock: Option<u8>, - /// If set, the stick will generate a code from the HOTP slot with the - /// given number if capslock is pressed. The slot number must be 0, 1 or 2. - pub capslock: Option<u8>, - /// If set, the stick will generate a code from the HOTP slot with the - /// given number if scrollock is pressed. The slot number must be 0, 1 or 2. - pub scrollock: Option<u8>, - /// If set, OTP generation using [`get_hotp_code`][] or [`get_totp_code`][] - /// requires user authentication. Otherwise, OTPs can be generated without - /// authentication. - /// - /// [`get_hotp_code`]: struct.Device.html#method.get_hotp_code - /// [`get_totp_code`]: struct.Device.html#method.get_totp_code - pub user_password: bool, -} - -#[derive(Debug)] -struct RawConfig { - pub numlock: u8, - pub capslock: u8, - pub scrollock: u8, - pub user_password: bool, -} - -/// A Nitrokey device without user or admin authentication. -/// -/// Use [`connect`][] or [`connect_model`][] to obtain an instance. If you -/// want to execute a command that requires user or admin authentication, -/// use [`authenticate_admin`][] or [`authenticate_user`][]. -/// -/// # Examples -/// -/// Authentication with error handling: -/// -/// ```no_run -/// use nitrokey::{UnauthenticatedDevice, UserAuthenticatedDevice}; -/// # use nitrokey::CommandError; -/// -/// fn perform_user_task(device: &UserAuthenticatedDevice) {} -/// fn perform_other_task(device: &UnauthenticatedDevice) {} -/// -/// # fn try_main() -> Result<(), CommandError> { -/// let device = nitrokey::connect()?; -/// let device = match device.authenticate_user("123456") { -/// Ok(user) => { -/// perform_user_task(&user); -/// user.device() -/// }, -/// Err((device, err)) => { -/// println!("Could not authenticate as user: {:?}", err); -/// device -/// }, -/// }; -/// perform_other_task(&device); -/// # Ok(()) -/// # } -/// ``` -/// -/// [`authenticate_admin`]: #method.authenticate_admin -/// [`authenticate_user`]: #method.authenticate_user -/// [`connect`]: fn.connect.html -/// [`connect_model`]: fn.connect_model.html -#[derive(Debug)] -pub struct UnauthenticatedDevice {} - -/// A Nitrokey device with user authentication. -/// -/// To obtain an instance of this struct, use the [`authenticate_user`][] -/// method on an [`UnauthenticatedDevice`][]. To get back to an -/// unauthenticated device, use the [`device`][] method. -/// -/// [`authenticate_admin`]: struct.UnauthenticatedDevice#method.authenticate_admin -/// [`device`]: #method.device -/// [`UnauthenticatedDevice`]: struct.UnauthenticatedDevice.html -#[derive(Debug)] -pub struct UserAuthenticatedDevice { - device: UnauthenticatedDevice, - temp_password: Vec<u8>, -} - -/// A Nitrokey device with admin authentication. -/// -/// To obtain an instance of this struct, use the [`authenticate_admin`][] -/// method on an [`UnauthenticatedDevice`][]. To get back to an -/// unauthenticated device, use the [`device`][] method. -/// -/// [`authenticate_admin`]: struct.UnauthenticatedDevice#method.authenticate_admin -/// [`device`]: #method.device -/// [`UnauthenticatedDevice`]: struct.UnauthenticatedDevice.html -#[derive(Debug)] -pub struct AdminAuthenticatedDevice { - device: UnauthenticatedDevice, - temp_password: Vec<u8>, -} - -/// 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)] -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, -} - -static TEMPORARY_PASSWORD_LENGTH: usize = 25; - -/// A Nitrokey device. -/// -/// This trait provides the commands that can be executed without -/// authentication. The only exception are the [`get_hotp_code`][] and -/// [`get_totp_code`][] methods: It depends on the device configuration -/// ([`get_config`][]) whether these commands require user authentication -/// or not. -/// -/// [`get_config`]: #method.get_config -/// [`get_hotp_code`]: #method.get_hotp_code -/// [`get_totp_code`]: #method.get_totp_code -pub trait Device { - /// 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`][]). - /// - /// # Errors - /// - /// - [`Timestamp`][] if the time could not be set - /// - /// [`get_totp_code`]: #method.get_totp_code - /// [`Timestamp`]: enum.CommandError.html#variant.Timestamp - // TODO: example - fn set_time(&self, time: u64) -> CommandStatus { - unsafe { CommandStatus::from(nitrokey_sys::NK_totp_set_time(time)) } - } - - /// Returns the serial number of the Nitrokey device. The serial number - /// is the string representation of a hex number. - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::CommandError; - /// - /// # fn try_main() -> Result<(), CommandError> { - /// let device = nitrokey::connect()?; - /// match device.get_serial_number() { - /// Ok(number) => println!("serial no: {:?}", number), - /// Err(err) => println!("Could not get serial number: {:?}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - fn get_serial_number(&self) -> Result<String, CommandError> { - unsafe { result_from_string(nitrokey_sys::NK_device_serial_number()) } - } - - /// 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, Device}; - /// - /// # 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, Device}; - /// - /// # 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)) } - } - - /// Returns the number of remaining authentication attempts for the user. The - /// total number of available attempts is three. - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::CommandError; - /// - /// # fn try_main() -> Result<(), CommandError> { - /// let device = nitrokey::connect()?; - /// let count = device.get_user_retry_count(); - /// println!("{} remaining authentication attempts (user)", count); - /// # Ok(()) - /// # } - /// ``` - fn get_user_retry_count(&self) -> u8 { - unsafe { nitrokey_sys::NK_get_user_retry_count() } - } - - /// Returns the number of remaining authentication attempts for the admin. The - /// total number of available attempts is three. - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::CommandError; - /// - /// # fn try_main() -> Result<(), CommandError> { - /// let device = nitrokey::connect()?; - /// let count = device.get_admin_retry_count(); - /// println!("{} remaining authentication attempts (admin)", count); - /// # Ok(()) - /// # } - /// ``` - fn get_admin_retry_count(&self) -> u8 { - unsafe { nitrokey_sys::NK_get_admin_retry_count() } - } - - /// Returns the major part of the firmware version (should be zero). - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::CommandError; - /// - /// # fn try_main() -> Result<(), CommandError> { - /// let device = nitrokey::connect()?; - /// println!( - /// "Firmware version: {}.{}", - /// device.get_major_firmware_version(), - /// device.get_minor_firmware_version(), - /// ); - /// # Ok(()) - /// # } - /// ``` - fn get_major_firmware_version(&self) -> i32 { - unsafe { nitrokey_sys::NK_get_major_firmware_version() } - } - - /// Returns the minor part of the firmware version (for example 8 for - /// version 0.8). - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::CommandError; - /// - /// # fn try_main() -> Result<(), CommandError> { - /// let device = nitrokey::connect()?; - /// println!( - /// "Firmware version: {}.{}", - /// device.get_major_firmware_version(), - /// device.get_minor_firmware_version(), - /// ); - /// # Ok(()) - /// # } - fn get_minor_firmware_version(&self) -> i32 { - unsafe { nitrokey_sys::NK_get_minor_firmware_version() } - } - - /// Returns the current configuration of the Nitrokey device. - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::CommandError; - /// - /// # fn try_main() -> Result<(), CommandError> { - /// let device = nitrokey::connect()?; - /// let config = device.get_config()?; - /// println!("numlock binding: {:?}", config.numlock); - /// println!("capslock binding: {:?}", config.capslock); - /// println!("scrollock binding: {:?}", config.scrollock); - /// println!("require password for OTP: {:?}", config.user_password); - /// # Ok(()) - /// # } - /// ``` - fn get_config(&self) -> Result<Config, CommandError> { - unsafe { - let config_ptr = nitrokey_sys::NK_read_config(); - if config_ptr.is_null() { - return Err(get_last_error()); - } - let config_array_ptr = config_ptr as *const [u8; 5]; - let raw_config = RawConfig::from(*config_array_ptr); - libc::free(config_ptr as *mut libc::c_void); - return Ok(raw_config.into()); - } - } - - /// 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::Device; - /// # 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`]: #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::Device; - /// # 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`]: #method.set_time - /// [`get_config`]: #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)); - } - } - - /// Changes the administrator PIN. - /// - /// # Errors - /// - /// - [`InvalidString`][] if one of the provided passwords contains a null byte - /// - [`WrongPassword`][] if the current admin password is wrong - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::{CommandStatus, Device}; - /// # use nitrokey::CommandError; - /// - /// # fn try_main() -> Result<(), CommandError> { - /// let device = nitrokey::connect()?; - /// match device.change_admin_pin("12345678", "12345679") { - /// CommandStatus::Success => println!("Updated admin PIN."), - /// CommandStatus::Error(err) => println!("Failed to update admin PIN: {:?}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - /// - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString - /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn change_admin_pin(&self, current: &str, new: &str) -> CommandStatus { - let current_string = CString::new(current); - let new_string = CString::new(new); - if current_string.is_err() || new_string.is_err() { - return CommandStatus::Error(CommandError::InvalidString); - } - let current_string = current_string.unwrap(); - let new_string = new_string.unwrap(); - unsafe { - CommandStatus::from(nitrokey_sys::NK_change_admin_PIN( - current_string.as_ptr(), - new_string.as_ptr(), - )) - } - } - - /// Changes the user PIN. - /// - /// # Errors - /// - /// - [`InvalidString`][] if one of the provided passwords contains a null byte - /// - [`WrongPassword`][] if the current user password is wrong - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::{CommandStatus, Device}; - /// # use nitrokey::CommandError; - /// - /// # fn try_main() -> Result<(), CommandError> { - /// let device = nitrokey::connect()?; - /// match device.change_user_pin("123456", "123457") { - /// CommandStatus::Success => println!("Updated admin PIN."), - /// CommandStatus::Error(err) => println!("Failed to update admin PIN: {:?}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - /// - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString - /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn change_user_pin(&self, current: &str, new: &str) -> CommandStatus { - let current_string = CString::new(current); - let new_string = CString::new(new); - if current_string.is_err() || new_string.is_err() { - return CommandStatus::Error(CommandError::InvalidString); - } - let current_string = current_string.unwrap(); - let new_string = new_string.unwrap(); - unsafe { - CommandStatus::from(nitrokey_sys::NK_change_user_PIN( - current_string.as_ptr(), - new_string.as_ptr(), - )) - } - } -} - -trait AuthenticatedDevice { - fn new(device: UnauthenticatedDevice, temp_password: Vec<u8>) -> Self; -} +pub use config::Config; +pub use device::{AdminAuthenticatedDevice, Device, Model, UnauthenticatedDevice, + UserAuthenticatedDevice}; +pub use otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData}; +pub use util::{CommandError, CommandStatus, LogLevel}; /// Connects to a Nitrokey device. This method can be used to connect to any /// connected device, both a Nitrokey Pro and a Nitrokey Storage. @@ -772,687 +179,3 @@ pub fn set_log_level(level: LogLevel) { nitrokey_sys::NK_set_debug_level(level.into()); } } - -fn config_otp_slot_to_option(value: u8) -> Option<u8> { - if value < 3 { - return Some(value); - } - None -} - -fn option_to_config_otp_slot(value: Option<u8>) -> Result<u8, CommandError> { - match value { - Some(value) => { - if value < 3 { - Ok(value) - } else { - Err(CommandError::InvalidSlot) - } - } - None => Ok(255), - } -} - -impl From<c_int> for CommandError { - fn from(value: c_int) -> Self { - match value { - 1 => CommandError::WrongCrc, - 2 => CommandError::WrongSlot, - 3 => CommandError::SlotNotProgrammed, - 4 => CommandError::WrongPassword, - 5 => CommandError::NotAuthorized, - 6 => CommandError::Timestamp, - 7 => CommandError::NoName, - 8 => CommandError::NotSupported, - 9 => CommandError::UnknownCommand, - 10 => CommandError::AesDecryptionFailed, - 201 => CommandError::InvalidSlot, - _ => CommandError::Unknown, - } - } -} - -impl From<c_int> for CommandStatus { - fn from(value: c_int) -> Self { - match value { - 0 => CommandStatus::Success, - other => CommandStatus::Error(CommandError::from(other)), - } - } -} - -impl Into<i32> for LogLevel { - fn into(self) -> i32 { - match self { - LogLevel::Error => 0, - LogLevel::Warning => 1, - LogLevel::Info => 2, - LogLevel::DebugL1 => 3, - LogLevel::Debug => 4, - LogLevel::DebugL2 => 5, - } - } -} - -fn get_last_status() -> CommandStatus { - unsafe { - let status = nitrokey_sys::NK_get_last_command_status(); - return CommandStatus::from(status as c_int); - } -} - -fn get_last_error() -> CommandError { - return match get_last_status() { - CommandStatus::Success => CommandError::Unknown, - CommandStatus::Error(err) => err, - }; -} - -fn owned_str_from_ptr(ptr: *const std::os::raw::c_char) -> String { - unsafe { - return CStr::from_ptr(ptr).to_string_lossy().into_owned(); - } -} - -fn result_from_string(ptr: *const std::os::raw::c_char) -> Result<String, CommandError> { - if ptr.is_null() { - return Err(CommandError::Unknown); - } - unsafe { - let s = owned_str_from_ptr(ptr); - if s.is_empty() { - return Err(get_last_error()); - } - // TODO: move up for newer libnitrokey versions - libc::free(ptr as *mut libc::c_void); - return Ok(s); - } -} - -fn generate_password(length: usize) -> std::io::Result<Vec<u8>> { - let mut rng = match rand::OsRng::new() { - Ok(rng) => rng, - Err(err) => return Err(err), - }; - let mut data = vec![0u8; length]; - rng.fill_bytes(&mut data[..]); - return Ok(data); -} - -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(), - }) - } -} - -impl Config { - /// Constructs a new instance of this struct. - pub fn new( - numlock: Option<u8>, - capslock: Option<u8>, - scrollock: Option<u8>, - user_password: bool, - ) -> Config { - Config { - numlock, - capslock, - scrollock, - user_password, - } - } -} - -impl RawConfig { - fn try_from(config: Config) -> Result<RawConfig, CommandError> { - Ok(RawConfig { - numlock: option_to_config_otp_slot(config.numlock)?, - capslock: option_to_config_otp_slot(config.capslock)?, - scrollock: option_to_config_otp_slot(config.scrollock)?, - user_password: config.user_password, - }) - } -} - -impl From<[u8; 5]> for RawConfig { - fn from(data: [u8; 5]) -> Self { - RawConfig { - numlock: data[0], - capslock: data[1], - scrollock: data[2], - user_password: data[3] != 0, - } - } -} - -impl Into<Config> for RawConfig { - fn into(self) -> Config { - Config { - numlock: config_otp_slot_to_option(self.numlock), - capslock: config_otp_slot_to_option(self.capslock), - scrollock: config_otp_slot_to_option(self.scrollock), - user_password: self.user_password, - } - } -} - -impl UnauthenticatedDevice { - fn authenticate<D, T>( - self, - password: &str, - callback: T, - ) -> Result<D, (UnauthenticatedDevice, CommandError)> - where - D: AuthenticatedDevice, - T: Fn(*const i8, *const i8) -> c_int, - { - let temp_password = match generate_password(TEMPORARY_PASSWORD_LENGTH) { - Ok(pw) => pw, - Err(_) => return Err((self, CommandError::RngError)), - }; - let password = CString::new(password); - if password.is_err() { - return Err((self, CommandError::InvalidString)); - } - - let pw = password.unwrap(); - let password_ptr = pw.as_ptr(); - let temp_password_ptr = temp_password.as_ptr() as *const i8; - return match callback(password_ptr, temp_password_ptr) { - 0 => Ok(D::new(self, temp_password)), - rv => Err((self, CommandError::from(rv))), - }; - } - - /// Performs user authentication. This method consumes the device. If - /// successful, an authenticated device is returned. Otherwise, the - /// current unauthenticated device and the error are returned. - /// - /// This method generates a random temporary password that is used for all - /// operations that require user access. - /// - /// # Errors - /// - /// - [`InvalidString`][] if the provided user password contains a null byte - /// - [`RngError`][] if the generation of the temporary password failed - /// - [`WrongPassword`][] if the provided user password is wrong - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::{UnauthenticatedDevice, UserAuthenticatedDevice}; - /// # use nitrokey::CommandError; - /// - /// fn perform_user_task(device: &UserAuthenticatedDevice) {} - /// fn perform_other_task(device: &UnauthenticatedDevice) {} - /// - /// # fn try_main() -> Result<(), CommandError> { - /// let device = nitrokey::connect()?; - /// let device = match device.authenticate_user("123456") { - /// Ok(user) => { - /// perform_user_task(&user); - /// user.device() - /// }, - /// Err((device, err)) => { - /// println!("Could not authenticate as user: {:?}", err); - /// device - /// }, - /// }; - /// perform_other_task(&device); - /// # Ok(()) - /// # } - /// ``` - /// - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString - /// [`RngError`]: enum.CommandError.html#variant.RngError - /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - pub fn authenticate_user( - self, - password: &str, - ) -> Result<UserAuthenticatedDevice, (UnauthenticatedDevice, CommandError)> { - return self.authenticate(password, |password_ptr, temp_password_ptr| unsafe { - nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr) - }); - } - - /// Performs admin authentication. This method consumes the device. If - /// successful, an authenticated device is returned. Otherwise, the - /// current unauthenticated device and the error are returned. - /// - /// This method generates a random temporary password that is used for all - /// operations that require admin access. - /// - /// # Errors - /// - /// - [`InvalidString`][] if the provided admin password contains a null byte - /// - [`RngError`][] if the generation of the temporary password failed - /// - [`WrongPassword`][] if the provided admin password is wrong - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::{AdminAuthenticatedDevice, UnauthenticatedDevice}; - /// # use nitrokey::CommandError; - /// - /// fn perform_admin_task(device: &AdminAuthenticatedDevice) {} - /// fn perform_other_task(device: &UnauthenticatedDevice) {} - /// - /// # fn try_main() -> Result<(), CommandError> { - /// let device = nitrokey::connect()?; - /// let device = match device.authenticate_admin("123456") { - /// Ok(admin) => { - /// perform_admin_task(&admin); - /// admin.device() - /// }, - /// Err((device, err)) => { - /// println!("Could not authenticate as admin: {:?}", err); - /// device - /// }, - /// }; - /// perform_other_task(&device); - /// # Ok(()) - /// # } - /// ``` - /// - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString - /// [`RngError`]: enum.CommandError.html#variant.RngError - /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - pub fn authenticate_admin( - self, - password: &str, - ) -> Result<AdminAuthenticatedDevice, (UnauthenticatedDevice, CommandError)> { - return self.authenticate(password, |password_ptr, temp_password_ptr| unsafe { - nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr) - }); - } -} - -impl Drop for UnauthenticatedDevice { - fn drop(&mut self) { - unsafe { - nitrokey_sys::NK_logout(); - } - } -} - -impl Device for UnauthenticatedDevice {} - -impl UserAuthenticatedDevice { - /// Forgets the user authentication and returns an unauthenticated - /// device. This method consumes the authenticated device. It does not - /// perform any actual commands on the Nitrokey. - pub fn device(self) -> UnauthenticatedDevice { - self.device - } -} - -impl Device for UserAuthenticatedDevice { - /// Generates an HOTP code on the given slot. This operation may not - /// require user authorization, depending on the device configuration (see - /// [`get_config`][]). - /// - /// # Errors - /// - /// - [`SlotNotProgrammed`][] if the given slot is not configured - /// - [`WrongSlot`][] if there is no slot with the given number - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::CommandError; - /// - /// # fn try_main() -> Result<(), CommandError> { - /// let device = nitrokey::connect()?; - /// match device.authenticate_user("123456") { - /// Ok(user) => { - /// let code = user.get_hotp_code(1)?; - /// println!("Generated HOTP code on slot 1: {:?}", code); - /// }, - /// Err(err) => println!("Could not authenticate: {:?}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - /// - /// [`get_config`]: #method.get_config - /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed - /// [`WrongSlot`]: enum.CommandError.html#variant.WrongSlot - fn get_hotp_code(&self, slot: u8) -> Result<String, CommandError> { - unsafe { - let temp_password_ptr = self.temp_password.as_ptr() as *const i8; - return result_from_string(nitrokey_sys::NK_get_hotp_code_PIN(slot, temp_password_ptr)); - } - } - - /// Generates a TOTP code on the given slot. This operation may not - /// 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 - /// - /// - [`SlotNotProgrammed`][] if the given slot is not configured - /// - [`WrongSlot`][] if there is no slot with the given number - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::CommandError; - /// - /// # fn try_main() -> Result<(), CommandError> { - /// let device = nitrokey::connect()?; - /// match device.authenticate_user("123456") { - /// Ok(user) => { - /// let code = user.get_totp_code(1)?; - /// println!("Generated TOTP code on slot 1: {:?}", code); - /// }, - /// Err(err) => println!("Could not authenticate: {:?}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - /// - /// [`get_config`]: #method.get_config - /// [`set_time`]: trait.Device.html#method.set_time - /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed - /// [`WrongSlot`]: enum.CommandError.html#variant.WrongSlot - fn get_totp_code(&self, slot: u8) -> Result<String, CommandError> { - unsafe { - let temp_password_ptr = self.temp_password.as_ptr() as *const i8; - return result_from_string(nitrokey_sys::NK_get_totp_code_PIN( - slot, - 0, - 0, - 0, - temp_password_ptr, - )); - } - } -} - -impl AuthenticatedDevice for UserAuthenticatedDevice { - fn new(device: UnauthenticatedDevice, temp_password: Vec<u8>) -> Self { - UserAuthenticatedDevice { - device, - temp_password, - } - } -} - -impl AdminAuthenticatedDevice { - /// Forgets the user authentication and returns an unauthenticated - /// device. This method consumes the authenticated device. It does not - /// perform any actual commands on the Nitrokey. - pub fn device(self) -> UnauthenticatedDevice { - self.device - } - - /// Writes the given configuration to the Nitrokey device. - /// - /// # Errors - /// - /// - [`InvalidSlot`][] if the provided numlock, capslock or scrolllock - /// slot is larger than two - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Config; - /// # use nitrokey::CommandError; - /// - /// # fn try_main() -> Result<(), CommandError> { - /// let device = nitrokey::connect()?; - /// let config = Config::new(None, None, None, false); - /// match device.authenticate_admin("12345678") { - /// Ok(admin) => { - /// admin.write_config(config); - /// () - /// }, - /// Err((_, err)) => println!("Could not authenticate as admin: {:?}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - /// - /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot - pub fn write_config(&self, config: Config) -> CommandStatus { - let raw_config = match RawConfig::try_from(config) { - Ok(raw_config) => raw_config, - Err(err) => return CommandStatus::Error(err), - }; - unsafe { - let rv = nitrokey_sys::NK_write_config( - raw_config.numlock, - raw_config.capslock, - raw_config.scrollock, - raw_config.user_password, - false, - self.temp_password.as_ptr() as *const i8, - ); - return CommandStatus::from(rv); - } - } - - fn write_otp_slot<T>(&self, data: OtpSlotData, callback: T) -> CommandStatus - where - T: Fn(RawOtpSlotData, *const i8) -> c_int, - { - let raw_data = match RawOtpSlotData::new(data) { - Ok(raw_data) => raw_data, - Err(err) => return CommandStatus::Error(err), - }; - let temp_password_ptr = self.temp_password.as_ptr() as *const i8; - let rv = callback(raw_data, temp_password_ptr); - return CommandStatus::from(rv); - } - - /// 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, Device, 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 - pub fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> CommandStatus { - return self.write_otp_slot(data, |raw_data: RawOtpSlotData, temp_password_ptr| unsafe { - nitrokey_sys::NK_write_hotp_slot( - raw_data.number, - raw_data.name.as_ptr(), - raw_data.secret.as_ptr(), - counter, - raw_data.mode == OtpMode::EightDigits, - raw_data.use_enter, - raw_data.use_token_id, - raw_data.token_id.as_ptr(), - temp_password_ptr, - ) - }); - } - - /// 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, Device, 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 - pub fn write_totp_slot(&self, data: OtpSlotData, time_window: u16) -> CommandStatus { - return self.write_otp_slot(data, |raw_data: RawOtpSlotData, temp_password_ptr| unsafe { - nitrokey_sys::NK_write_totp_slot( - raw_data.number, - raw_data.name.as_ptr(), - raw_data.secret.as_ptr(), - time_window, - raw_data.mode == OtpMode::EightDigits, - raw_data.use_enter, - raw_data.use_token_id, - raw_data.token_id.as_ptr(), - temp_password_ptr, - ) - }); - } - - /// Erase an HOTP slot. - /// - /// # Errors - /// - /// - [`InvalidSlot`][] if there is no slot with the given number - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::{CommandStatus, Device, OtpMode, OtpSlotData}; - /// # 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 - pub fn erase_hotp_slot(&self, slot: u8) -> CommandStatus { - let temp_password_ptr = self.temp_password.as_ptr() as *const i8; - unsafe { CommandStatus::from(nitrokey_sys::NK_erase_hotp_slot(slot, temp_password_ptr)) } - } - - /// Erase a TOTP slot. - /// - /// # Errors - /// - /// - [`InvalidSlot`][] if there is no slot with the given number - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::{CommandStatus, Device, OtpMode, OtpSlotData}; - /// # 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 - pub fn erase_totp_slot(&self, slot: u8) -> CommandStatus { - let temp_password_ptr = self.temp_password.as_ptr() as *const i8; - unsafe { CommandStatus::from(nitrokey_sys::NK_erase_totp_slot(slot, temp_password_ptr)) } - } -} - -impl Device for AdminAuthenticatedDevice {} - -impl AuthenticatedDevice for AdminAuthenticatedDevice { - fn new(device: UnauthenticatedDevice, temp_password: Vec<u8>) -> Self { - AdminAuthenticatedDevice { - device, - temp_password, - } - } -} |