use device::{Device, DeviceWrapper, Pro, Storage}; use libc; use nitrokey_sys; use std::ffi::CString; use util::{get_last_error, result_from_string, CommandError, CommandStatus}; /// The number of slots in a [`PasswordSafe`][]. /// /// [`PasswordSafe`]: struct.PasswordSafe.html pub const SLOT_COUNT: u8 = 16; /// A password safe on a Nitrokey device. /// /// The password safe stores a tuple consisting of a name, a login and a password on a slot. The /// number of available slots is [`SLOT_COUNT`][]. The slots are addressed starting with zero. /// /// The password safe struct wraps a device. To get back to the original device, use the /// [`device`][] method. To retrieve a password safe from a Nitrokey device, use the /// [`get_password_safe`][] method from the [`GetPasswordSafe`][] trait. /// /// # Examples /// /// Open a password safe and access a password: /// /// ```no_run /// use nitrokey::{Device, GetPasswordSafe}; /// # use nitrokey::CommandError; /// /// fn use_device(device: &T) {} /// /// # fn try_main() -> Result<(), CommandError> { /// let device = nitrokey::connect()?; /// let device = match device.get_password_safe("123456") { /// Ok(pws) => { /// let name = pws.get_slot_name(0)?; /// let login = pws.get_slot_login(0)?; /// let password = pws.get_slot_login(0)?; /// println!("Credentials for {}: login {}, password {}", name, login, password); /// pws.device() /// }, /// Err((device, err)) => { /// println!("Could not open the password safe: {:?}", err); /// device /// }, /// }; /// use_device(&device); /// # Ok(()) /// # } /// ``` /// /// [`SLOT_COUNT`]: constant.SLOT_COUNT.html /// [`device`]: #method.device /// [`get_password_safe`]: trait.GetPasswordSafe.html#method.get_password_safe /// [`GetPasswordSafe`]: trait.GetPasswordSafe.html pub struct PasswordSafe { device: T, } /// Provides access to a [`PasswordSafe`][]. /// /// When retrieving a password safe, the underlying device is consumed. On success, the returned /// password safe also containes the device. On error, the device is returned as part of the /// error. /// /// [`PasswordSafe`]: struct.PasswordSafe.html pub trait GetPasswordSafe { /// Enables and returns the password safe. This method consumes the device. You can go back /// to the device using the [`device`][] method of the returned password safe. If the method /// fails, the current device will be returned as part of the error. /// /// # 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, GetPasswordSafe, PasswordSafe}; /// # use nitrokey::CommandError; /// /// fn use_password_safe(pws: &PasswordSafe) {} /// fn use_device(device: &T) {} /// /// # fn try_main() -> Result<(), CommandError> { /// let device = nitrokey::connect()?; /// let device = match device.get_password_safe("123456") { /// Ok(pws) => { /// use_password_safe(&pws); /// pws.device() /// }, /// Err((device, err)) => { /// println!("Could not open the password safe: {:?}", err); /// device /// }, /// }; /// use_device(&device); /// # Ok(()) /// # } /// ``` /// /// [`device`]: struct.PasswordSafe.html#method.device /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword fn get_password_safe(self, user_pin: &str) -> Result, (Self, CommandError)> where Self: Device + Sized; } fn get_password_safe( device: T, user_pin: &str, ) -> Result, (T, CommandError)> { let user_pin_string = CString::new(user_pin); if user_pin_string.is_err() { return Err((device, CommandError::InvalidString)); } let user_pin_string = user_pin_string.unwrap(); let status = unsafe { CommandStatus::from(nitrokey_sys::NK_enable_password_safe( user_pin_string.as_ptr(), )) }; match status { CommandStatus::Success => Ok(PasswordSafe { device }), CommandStatus::Error(err) => Err((device, err)), } } fn get_password_safe_wrapper( device: T, constructor: C, user_pin: &str, ) -> Result, (DeviceWrapper, CommandError)> where T: Device, C: Fn(T) -> DeviceWrapper, { let result = device.get_password_safe(user_pin); match result { Ok(pws) => Ok(PasswordSafe { device: constructor(pws.device), }), Err((device, err)) => Err((constructor(device), err)), } } impl PasswordSafe { /// Forgets the password safe access and returns an unauthenticated device. This method /// consumes the password safe. It does not perform any actual commands on the Nitrokey. pub fn device(self) -> T { self.device } /// Returns the status of all password slots. /// /// The status indicates whether a slot is programmed or not. /// /// # Example /// /// ```no_run /// use nitrokey::{GetPasswordSafe, SLOT_COUNT}; /// # use nitrokey::CommandError; /// /// # fn try_main() -> Result<(), CommandError> { /// let device = nitrokey::connect()?; /// match device.get_password_safe("123456") { /// Ok(pws) => { /// pws.get_slot_status()?.iter().enumerate().for_each(|(slot, programmed)| { /// let status = match *programmed { /// true => "programmed", /// false => "not programmed", /// }; /// println!("Slot {}: {}", slot, status); /// }); /// }, /// Err((_, err)) => println!("Could not open the password safe: {:?}", err), /// }; /// # Ok(()) /// # } /// ``` pub fn get_slot_status(&self) -> Result<[bool; SLOT_COUNT as usize], CommandError> { let status_ptr = unsafe { nitrokey_sys::NK_get_password_safe_slot_status() }; if status_ptr.is_null() { return Err(get_last_error()); } let status_array_ptr = status_ptr as *const [u8; SLOT_COUNT as usize]; let status_array = unsafe { *status_array_ptr }; let mut result = [false; SLOT_COUNT as usize]; for i in 0..SLOT_COUNT { result[i as usize] = status_array[i as usize] == 1; } unsafe { libc::free(status_ptr as *mut libc::c_void); } Ok(result) } /// Returns the name of the given slot (if it is programmed). /// /// # Errors /// /// - [`InvalidSlot`][] if the given slot is out of range /// - [`Unknown`][] if the slot is not programmed /// /// # Example /// /// ```no_run /// use nitrokey::GetPasswordSafe; /// # use nitrokey::CommandError; /// /// # fn try_main() -> Result<(), CommandError> { /// let device = nitrokey::connect()?; /// match device.get_password_safe("123456") { /// Ok(pws) => { /// let name = pws.get_slot_name(0)?; /// let login = pws.get_slot_login(0)?; /// let password = pws.get_slot_login(0)?; /// println!("Credentials for {}: login {}, password {}", name, login, password); /// }, /// Err((_, err)) => println!("Could not open the password safe: {:?}", err), /// }; /// # Ok(()) /// # } /// ``` /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot /// [`Unknown`]: enum.CommandError.html#variant.Unknown pub fn get_slot_name(&self, slot: u8) -> Result { unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_name(slot)) } } /// Returns the login for the given slot (if it is programmed). /// /// # Errors /// /// - [`InvalidSlot`][] if the given slot is out of range /// - [`Unknown`][] if the slot is not programmed /// /// # Example /// /// ```no_run /// use nitrokey::GetPasswordSafe; /// # use nitrokey::CommandError; /// /// # fn try_main() -> Result<(), CommandError> { /// let device = nitrokey::connect()?; /// match device.get_password_safe("123456") { /// Ok(pws) => { /// let name = pws.get_slot_name(0)?; /// let login = pws.get_slot_login(0)?; /// let password = pws.get_slot_login(0)?; /// println!("Credentials for {}: login {}, password {}", name, login, password); /// }, /// Err((_, err)) => println!("Could not open the password safe: {:?}", err), /// }; /// # Ok(()) /// # } /// ``` /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot /// [`Unknown`]: enum.CommandError.html#variant.Unknown pub fn get_slot_login(&self, slot: u8) -> Result { unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_login(slot)) } } /// Returns the password for the given slot (if it is programmed). /// /// # Errors /// /// - [`InvalidSlot`][] if the given slot is out of range /// - [`Unknown`][] if the slot is not programmed /// /// # Example /// /// ```no_run /// use nitrokey::GetPasswordSafe; /// # use nitrokey::CommandError; /// /// # fn try_main() -> Result<(), CommandError> { /// let device = nitrokey::connect()?; /// match device.get_password_safe("123456") { /// Ok(pws) => { /// let name = pws.get_slot_name(0)?; /// let login = pws.get_slot_login(0)?; /// let password = pws.get_slot_login(0)?; /// println!("Credentials for {}: login {}, password {}", name, login, password); /// }, /// Err((_, err)) => println!("Could not open the password safe: {:?}", err), /// }; /// # Ok(()) /// # } /// ``` /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot /// [`Unknown`]: enum.CommandError.html#variant.Unknown pub fn get_slot_password(&self, slot: u8) -> Result { unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_password(slot)) } } /// Writes the given slot with the given name, login and password. /// /// # Errors /// /// - [`InvalidSlot`][] if the given slot is out of range /// - [`InvalidString`][] if the provided token ID contains a null byte /// /// # Example /// /// ```no_run /// use nitrokey::{CommandStatus, GetPasswordSafe}; /// # use nitrokey::CommandError; /// /// # fn try_main() -> Result<(), CommandError> { /// let device = nitrokey::connect()?; /// match device.get_password_safe("123456") { /// Ok(pws) => match pws.write_slot(0, "GitHub", "johndoe", "passw0rd") { /// CommandStatus::Success => println!("Successfully wrote slot 0."), /// CommandStatus::Error(err) => println!("Could not write slot 0: {:?}", err), /// }, /// Err((_, err)) => println!("Could not open the password safe: {:?}", err), /// }; /// # Ok(()) /// # } /// ``` /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString pub fn write_slot(&self, slot: u8, name: &str, login: &str, password: &str) -> CommandStatus { let name_string = CString::new(name); let login_string = CString::new(login); let password_string = CString::new(password); if name_string.is_err() || login_string.is_err() || password_string.is_err() { return CommandStatus::Error(CommandError::InvalidString); } let name_string = name_string.unwrap(); let login_string = login_string.unwrap(); let password_string = password_string.unwrap(); unsafe { CommandStatus::from(nitrokey_sys::NK_write_password_safe_slot( slot, name_string.as_ptr(), login_string.as_ptr(), password_string.as_ptr(), )) } } /// Erases the given slot. Erasing clears the stored name, login and password (if the slot was /// programmed). /// /// # Errors /// /// - [`InvalidSlot`][] if the given slot is out of range /// /// # Example /// /// ```no_run /// use nitrokey::{CommandStatus, GetPasswordSafe}; /// # use nitrokey::CommandError; /// /// # fn try_main() -> Result<(), CommandError> { /// let device = nitrokey::connect()?; /// match device.get_password_safe("123456") { /// Ok(pws) => match pws.erase_slot(0) { /// CommandStatus::Success => println!("Successfully erased slot 0."), /// CommandStatus::Error(err) => println!("Could not erase slot 0: {:?}", err), /// }, /// Err((_, err)) => println!("Could not open the password safe: {:?}", err), /// }; /// # Ok(()) /// # } /// ``` /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot pub fn erase_slot(&self, slot: u8) -> CommandStatus { unsafe { CommandStatus::from(nitrokey_sys::NK_erase_password_safe_slot(slot)) } } } impl GetPasswordSafe for Pro { fn get_password_safe(self, user_pin: &str) -> Result, (Self, CommandError)> { get_password_safe(self, user_pin) } } impl GetPasswordSafe for Storage { fn get_password_safe(self, user_pin: &str) -> Result, (Self, CommandError)> { get_password_safe(self, user_pin) } } impl GetPasswordSafe for DeviceWrapper { fn get_password_safe(self, user_pin: &str) -> Result, (Self, CommandError)> { match self { DeviceWrapper::Storage(storage) => { get_password_safe_wrapper(storage, DeviceWrapper::Storage, user_pin) } DeviceWrapper::Pro(pro) => get_password_safe_wrapper(pro, DeviceWrapper::Pro, user_pin), } } }