// Copyright (C) 2018-2019 Robin Krahl // SPDX-License-Identifier: MIT mod pro; mod storage; mod wrapper; use std::fmt; use libc; use nitrokey_sys; use crate::auth::Authenticate; use crate::config::{Config, RawConfig}; use crate::error::{CommunicationError, Error}; use crate::otp::GenerateOtp; use crate::pws::GetPasswordSafe; use crate::util::{ get_command_result, get_cstring, get_last_error, result_from_string, result_or_error, }; pub use pro::Pro; pub use storage::{ SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus, }; pub use wrapper::DeviceWrapper; /// Available Nitrokey models. #[derive(Clone, Copy, Debug, PartialEq)] pub enum Model { /// The Nitrokey Storage. Storage, /// The Nitrokey Pro. Pro, } impl fmt::Display for Model { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match *self { Model::Pro => "Pro", Model::Storage => "Storage", }) } } /// A firmware version for a Nitrokey device. #[derive(Clone, Copy, Debug, PartialEq)] pub struct FirmwareVersion { /// The major firmware version, e. g. 0 in v0.40. pub major: u8, /// The minor firmware version, e. g. 40 in v0.40. pub minor: u8, } impl fmt::Display for FirmwareVersion { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "v{}.{}", self.major, self.minor) } } /// A Nitrokey device. /// /// This trait provides the commands that can be executed without authentication and that are /// present on all supported Nitrokey devices. pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt::Debug { /// Returns the [`Manager`][] instance that has been used to connect to this device. /// /// # Example /// /// ``` /// use nitrokey::{Device, DeviceWrapper}; /// /// fn do_something(device: DeviceWrapper) { /// // reconnect to any device /// let manager = device.into_manager(); /// let device = manager.connect(); /// // do something with the device /// // ... /// } /// /// # fn main() -> Result<(), nitrokey::Error> { /// match nitrokey::take()?.connect() { /// Ok(device) => do_something(device), /// Err(err) => println!("Could not connect to a Nitrokey: {}", err), /// } /// # Ok(()) /// # } /// ``` fn into_manager(self) -> &'a mut crate::Manager; /// Returns the model of the connected Nitrokey device. /// /// # Example /// /// ```no_run /// use nitrokey::Device; /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { /// let mut manager = nitrokey::take()?; /// let device = manager.connect()?; /// println!("Connected to a Nitrokey {}", device.get_model()); /// # Ok(()) /// # } fn get_model(&self) -> Model; /// 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::Error; /// /// # fn try_main() -> Result<(), Error> { /// let mut manager = nitrokey::take()?; /// let device = manager.connect()?; /// match device.get_serial_number() { /// Ok(number) => println!("serial no: {}", number), /// Err(err) => eprintln!("Could not get serial number: {}", err), /// }; /// # Ok(()) /// # } /// ``` fn get_serial_number(&self) -> Result { result_from_string(unsafe { nitrokey_sys::NK_device_serial_number() }) } /// 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::Error; /// /// # fn try_main() -> Result<(), Error> { /// let mut manager = nitrokey::take()?; /// let device = manager.connect()?; /// let count = device.get_user_retry_count(); /// match device.get_user_retry_count() { /// Ok(count) => println!("{} remaining authentication attempts (user)", count), /// Err(err) => eprintln!("Could not get user retry count: {}", err), /// } /// # Ok(()) /// # } /// ``` fn get_user_retry_count(&self) -> Result { result_or_error(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::Error; /// /// # fn try_main() -> Result<(), Error> { /// let mut manager = nitrokey::take()?; /// let device = manager.connect()?; /// let count = device.get_admin_retry_count(); /// match device.get_admin_retry_count() { /// Ok(count) => println!("{} remaining authentication attempts (admin)", count), /// Err(err) => eprintln!("Could not get admin retry count: {}", err), /// } /// # Ok(()) /// # } /// ``` fn get_admin_retry_count(&self) -> Result { result_or_error(unsafe { nitrokey_sys::NK_get_admin_retry_count() }) } /// Returns the firmware version. /// /// # Example /// /// ```no_run /// use nitrokey::Device; /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { /// let mut manager = nitrokey::take()?; /// let device = manager.connect()?; /// match device.get_firmware_version() { /// Ok(version) => println!("Firmware version: {}", version), /// Err(err) => eprintln!("Could not access firmware version: {}", err), /// }; /// # Ok(()) /// # } /// ``` fn get_firmware_version(&self) -> Result { let major = result_or_error(unsafe { nitrokey_sys::NK_get_major_firmware_version() })?; let minor = result_or_error(unsafe { nitrokey_sys::NK_get_minor_firmware_version() })?; Ok(FirmwareVersion { major, minor }) } /// Returns the current configuration of the Nitrokey device. /// /// # Example /// /// ```no_run /// use nitrokey::Device; /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { /// let mut manager = nitrokey::take()?; /// let device = manager.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 { let config_ptr = unsafe { 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 = unsafe { RawConfig::from(*config_array_ptr) }; unsafe { libc::free(config_ptr as *mut libc::c_void) }; Ok(raw_config.into()) } /// 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::Device; /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { /// let mut manager = nitrokey::take()?; /// let mut device = manager.connect()?; /// match device.change_admin_pin("12345678", "12345679") { /// Ok(()) => println!("Updated admin PIN."), /// Err(err) => eprintln!("Failed to update admin PIN: {}", err), /// }; /// # Ok(()) /// # } /// ``` /// /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword fn change_admin_pin(&mut self, current: &str, new: &str) -> Result<(), Error> { let current_string = get_cstring(current)?; let new_string = get_cstring(new)?; get_command_result(unsafe { 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::Device; /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { /// let mut manager = nitrokey::take()?; /// let mut device = manager.connect()?; /// match device.change_user_pin("123456", "123457") { /// Ok(()) => println!("Updated admin PIN."), /// Err(err) => eprintln!("Failed to update admin PIN: {}", err), /// }; /// # Ok(()) /// # } /// ``` /// /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword fn change_user_pin(&mut self, current: &str, new: &str) -> Result<(), Error> { let current_string = get_cstring(current)?; let new_string = get_cstring(new)?; get_command_result(unsafe { nitrokey_sys::NK_change_user_PIN(current_string.as_ptr(), new_string.as_ptr()) }) } /// Unlocks the user PIN after three failed login attempts and sets it to the given value. /// /// # Errors /// /// - [`InvalidString`][] if one of the provided passwords contains a null byte /// - [`WrongPassword`][] if the admin password is wrong /// /// # Example /// /// ```no_run /// use nitrokey::Device; /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { /// let mut manager = nitrokey::take()?; /// let mut device = manager.connect()?; /// match device.unlock_user_pin("12345678", "123456") { /// Ok(()) => println!("Unlocked user PIN."), /// Err(err) => eprintln!("Failed to unlock user PIN: {}", err), /// }; /// # Ok(()) /// # } /// ``` /// /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword fn unlock_user_pin(&mut self, admin_pin: &str, user_pin: &str) -> Result<(), Error> { let admin_pin_string = get_cstring(admin_pin)?; let user_pin_string = get_cstring(user_pin)?; get_command_result(unsafe { nitrokey_sys::NK_unlock_user_password( admin_pin_string.as_ptr(), user_pin_string.as_ptr(), ) }) } /// Locks the Nitrokey device. /// /// This disables the password store if it has been unlocked. On the Nitrokey Storage, this /// also disables the volumes if they have been enabled. /// /// # Example /// /// ```no_run /// use nitrokey::Device; /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { /// let mut manager = nitrokey::take()?; /// let mut device = manager.connect()?; /// match device.lock() { /// Ok(()) => println!("Locked the Nitrokey device."), /// Err(err) => eprintln!("Could not lock the Nitrokey device: {}", err), /// }; /// # Ok(()) /// # } /// ``` fn lock(&mut self) -> Result<(), Error> { get_command_result(unsafe { nitrokey_sys::NK_lock_device() }) } /// Performs a factory reset on the Nitrokey device. /// /// This commands performs a factory reset on the smart card (like the factory reset via `gpg /// --card-edit`) and then clears the flash memory (password safe, one-time passwords etc.). /// After a factory reset, [`build_aes_key`][] has to be called before the password safe or the /// encrypted volume can be used. /// /// # Errors /// /// - [`InvalidString`][] if the provided password contains a null byte /// - [`WrongPassword`][] if the admin password is wrong /// /// # Example /// /// ```no_run /// use nitrokey::Device; /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { /// let mut manager = nitrokey::take()?; /// let mut device = manager.connect()?; /// match device.factory_reset("12345678") { /// Ok(()) => println!("Performed a factory reset."), /// Err(err) => eprintln!("Could not perform a factory reset: {}", err), /// }; /// # Ok(()) /// # } /// ``` /// /// [`build_aes_key`]: #method.build_aes_key fn factory_reset(&mut self, admin_pin: &str) -> Result<(), Error> { let admin_pin_string = get_cstring(admin_pin)?; get_command_result(unsafe { nitrokey_sys::NK_factory_reset(admin_pin_string.as_ptr()) }) } /// Builds a new AES key on the Nitrokey. /// /// The AES key is used to encrypt the password safe and the encrypted volume. You may need /// to call this method after a factory reset, either using [`factory_reset`][] or using `gpg /// --card-edit`. You can also use it to destroy the data stored in the password safe or on /// the encrypted volume. /// /// # Errors /// /// - [`InvalidString`][] if the provided password contains a null byte /// - [`WrongPassword`][] if the admin password is wrong /// /// # Example /// /// ```no_run /// use nitrokey::Device; /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { /// let mut manager = nitrokey::take()?; /// let mut device = manager.connect()?; /// match device.build_aes_key("12345678") { /// Ok(()) => println!("New AES keys have been built."), /// Err(err) => eprintln!("Could not build new AES keys: {}", err), /// }; /// # Ok(()) /// # } /// ``` /// /// [`factory_reset`]: #method.factory_reset fn build_aes_key(&mut self, admin_pin: &str) -> Result<(), Error> { let admin_pin_string = get_cstring(admin_pin)?; get_command_result(unsafe { nitrokey_sys::NK_build_aes_key(admin_pin_string.as_ptr()) }) } } fn get_connected_model() -> Option { match unsafe { nitrokey_sys::NK_get_device_model() } { nitrokey_sys::NK_device_model_NK_PRO => Some(Model::Pro), nitrokey_sys::NK_device_model_NK_STORAGE => Some(Model::Storage), _ => None, } } pub(crate) fn create_device_wrapper( manager: &mut crate::Manager, model: Model, ) -> DeviceWrapper<'_> { match model { Model::Pro => Pro::new(manager).into(), Model::Storage => Storage::new(manager).into(), } } pub(crate) fn get_connected_device( manager: &mut crate::Manager, ) -> Result, Error> { match get_connected_model() { Some(model) => Ok(create_device_wrapper(manager, model)), None => Err(CommunicationError::NotConnected.into()), } } pub(crate) fn connect_enum(model: Model) -> bool { let model = match model { Model::Storage => nitrokey_sys::NK_device_model_NK_STORAGE, Model::Pro => nitrokey_sys::NK_device_model_NK_PRO, }; unsafe { nitrokey_sys::NK_login_enum(model) == 1 } }