use libc; use nitrokey_sys; use crate::device::{Device, DeviceWrapper, Pro, Storage}; use crate::error::CommandError; use crate::util::{get_command_result, get_cstring, get_last_error, result_from_string}; /// 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. To /// retrieve a password safe from a Nitrokey device, use the [`get_password_safe`][] method from /// the [`GetPasswordSafe`][] trait. Note that the device must live at least as long as the /// password safe. /// /// Once the password safe has been unlocked, it can be accessed without a password. Therefore it /// is mandatory to call [`lock`][] on the corresponding device after the password store is used. /// As this command may have side effects on the Nitrokey Storage, it cannot be called /// automatically once the password safe is destroyed. /// /// # Examples /// /// Open a password safe and access a password: /// /// ```no_run /// use nitrokey::{Device, GetPasswordSafe, PasswordSafe}; /// # use nitrokey::CommandError; /// /// fn use_password_safe(pws: &PasswordSafe) -> Result<(), CommandError> { /// 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); /// Ok(()) /// } /// /// # fn try_main() -> Result<(), CommandError> { /// let device = nitrokey::connect()?; /// let pws = device.get_password_safe("123456")?; /// use_password_safe(&pws); /// device.lock()?; /// # Ok(()) /// # } /// ``` /// /// [`SLOT_COUNT`]: constant.SLOT_COUNT.html /// [`get_password_safe`]: trait.GetPasswordSafe.html#method.get_password_safe /// [`lock`]: trait.Device.html#method.lock /// [`GetPasswordSafe`]: trait.GetPasswordSafe.html pub struct PasswordSafe<'a> { _device: &'a dyn Device, } /// Provides access to a [`PasswordSafe`][]. /// /// The device that implements this trait must always live at least as long as a password safe /// retrieved from it. /// /// [`PasswordSafe`]: struct.PasswordSafe.html pub trait GetPasswordSafe { /// Enables and returns the password safe. /// /// The underlying device must always live at least as long as a password safe retrieved from /// it. It is mandatory to lock the underlying device using [`lock`][] after the password safe /// has been used. Otherwise, other applications can access the password store without /// authentication. /// /// If this method returns an `AesDecryptionFailed` (Nitrokey Pro) or `Unknown` (Nitrokey /// Storage) error, the AES data object on the smart card could not be accessed. This problem /// occurs after a factory reset using `gpg --card-edit` and can be fixed using the /// [`Device::build_aes_key`][] command. /// /// # Errors /// /// - [`AesDecryptionFailed`][] if the secret for the password safe could not be decrypted /// (Nitrokey Pro only) /// - [`InvalidString`][] if one of the provided passwords contains a null byte /// - [`Unknown`][] if the secret for the password safe could not be decrypted (Nitrokey /// Storage only) /// - [`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 try_main() -> Result<(), CommandError> { /// let device = nitrokey::connect()?; /// match device.get_password_safe("123456") { /// Ok(pws) => { /// use_password_safe(&pws); /// device.lock()?; /// }, /// Err(err) => println!("Could not open the password safe: {}", err), /// }; /// # Ok(()) /// # } /// ``` /// /// [`device`]: struct.PasswordSafe.html#method.device /// [`lock`]: trait.Device.html#method.lock /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed /// [`Device::build_aes_key`]: trait.Device.html#method.build_aes_key /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString /// [`Unknown`]: enum.CommandError.html#variant.Unknown /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword fn get_password_safe(&self, user_pin: &str) -> Result, CommandError>; } fn get_password_safe<'a>( device: &'a dyn Device, user_pin: &str, ) -> Result, CommandError> { let user_pin_string = get_cstring(user_pin)?; let result = unsafe { get_command_result(nitrokey_sys::NK_enable_password_safe( user_pin_string.as_ptr(), )) }; result.map(|()| PasswordSafe { _device: device }) } fn get_pws_result(s: String) -> Result { if s.is_empty() { Err(CommandError::SlotNotProgrammed) } else { Ok(s) } } impl<'a> PasswordSafe<'a> { /// 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()?; /// let pws = device.get_password_safe("123456")?; /// pws.get_slot_status()?.iter().enumerate().for_each(|(slot, programmed)| { /// let status = match *programmed { /// true => "programmed", /// false => "not programmed", /// }; /// println!("Slot {}: {}", slot, status); /// }); /// # 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). /// /// This method also returns a `SlotNotProgrammed` error if the name is empty. /// /// # Errors /// /// - [`InvalidSlot`][] if the given slot is out of range /// - [`SlotNotProgrammed`][] 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 /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed pub fn get_slot_name(&self, slot: u8) -> Result { unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_name(slot)) } .and_then(get_pws_result) } /// Returns the login for the given slot (if it is programmed). /// /// This method also returns a `SlotNotProgrammed` error if the login is empty. /// /// # Errors /// /// - [`InvalidSlot`][] if the given slot is out of range /// - [`SlotNotProgrammed`][] if the slot is not programmed /// /// # Example /// /// ```no_run /// use nitrokey::GetPasswordSafe; /// # use nitrokey::CommandError; /// /// # fn try_main() -> Result<(), CommandError> { /// let device = nitrokey::connect()?; /// let pws = device.get_password_safe("123456")?; /// 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); /// # Ok(()) /// # } /// ``` /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed pub fn get_slot_login(&self, slot: u8) -> Result { unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_login(slot)) } .and_then(get_pws_result) } /// Returns the password for the given slot (if it is programmed). /// /// This method also returns a `SlotNotProgrammed` error if the password is empty. /// /// # Errors /// /// - [`InvalidSlot`][] if the given slot is out of range /// - [`SlotNotProgrammed`][] if the slot is not programmed /// /// # Example /// /// ```no_run /// use nitrokey::GetPasswordSafe; /// # use nitrokey::CommandError; /// /// # fn try_main() -> Result<(), CommandError> { /// let device = nitrokey::connect()?; /// let pws = device.get_password_safe("123456")?; /// 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); /// # Ok(()) /// # } /// ``` /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed pub fn get_slot_password(&self, slot: u8) -> Result { unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_password(slot)) } .and_then(get_pws_result) } /// 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::GetPasswordSafe; /// # use nitrokey::CommandError; /// /// # fn try_main() -> Result<(), CommandError> { /// let device = nitrokey::connect()?; /// let pws = device.get_password_safe("123456")?; /// 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); /// # 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, ) -> Result<(), CommandError> { let name_string = get_cstring(name)?; let login_string = get_cstring(login)?; let password_string = get_cstring(password)?; unsafe { get_command_result(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::GetPasswordSafe; /// # use nitrokey::CommandError; /// /// # fn try_main() -> Result<(), CommandError> { /// let device = nitrokey::connect()?; /// let pws = device.get_password_safe("123456")?; /// match pws.erase_slot(0) { /// Ok(()) => println!("Erased slot 0."), /// Err(err) => println!("Could not erase slot 0: {}", err), /// }; /// # Ok(()) /// # } /// ``` /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot pub fn erase_slot(&self, slot: u8) -> Result<(), CommandError> { unsafe { get_command_result(nitrokey_sys::NK_erase_password_safe_slot(slot)) } } } impl<'a> Drop for PasswordSafe<'a> { fn drop(&mut self) { // TODO: disable the password safe -- NK_lock_device has side effects on the Nitrokey // Storage, see https://github.com/Nitrokey/nitrokey-storage-firmware/issues/65 } } impl GetPasswordSafe for Pro { fn get_password_safe(&self, user_pin: &str) -> Result, CommandError> { get_password_safe(self, user_pin) } } impl GetPasswordSafe for Storage { fn get_password_safe(&self, user_pin: &str) -> Result, CommandError> { get_password_safe(self, user_pin) } } impl GetPasswordSafe for DeviceWrapper { fn get_password_safe(&self, user_pin: &str) -> Result, CommandError> { get_password_safe(self, user_pin) } }