aboutsummaryrefslogtreecommitdiff
path: root/src/pws.rs
diff options
context:
space:
mode:
authorRobin Krahl <robin.krahl@ireas.org>2018-05-29 20:49:40 +0000
committerRobin Krahl <robin.krahl@ireas.org>2018-05-29 22:53:02 +0200
commit7197f19f38b06fe2953cfba1fe755d4562f5786e (patch)
tree972e6759f8eafe98669688f7dd44e0acee148331 /src/pws.rs
parent89b8a947e5c622272362e967847eb19337aa68da (diff)
downloadnitrokey-rs-7197f19f38b06fe2953cfba1fe755d4562f5786e.tar.gz
nitrokey-rs-7197f19f38b06fe2953cfba1fe755d4562f5786e.tar.bz2
Add support for password safes
A password safe (PWS) stores names, logins and passwords in slots. PWS are supported both by the Nitrokey Pro and the Nitrokey Storage. They are implemented as a struct wrapping a device as the device may not be disconnected while the password safe is alive. The creation of a password safe is handled by the GetPasswordSafe trait, implemented by DeviceWrapper, Pro and Storage.
Diffstat (limited to 'src/pws.rs')
-rw-r--r--src/pws.rs403
1 files changed, 403 insertions, 0 deletions
diff --git a/src/pws.rs b/src/pws.rs
new file mode 100644
index 0000000..fc4b516
--- /dev/null
+++ b/src/pws.rs
@@ -0,0 +1,403 @@
+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<T: 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<T: Device> {
+ 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<T: Device>(pws: &PasswordSafe<T>) {}
+ /// fn use_device<T: 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<PasswordSafe<Self>, (Self, CommandError)>
+ where
+ Self: Device + Sized;
+}
+
+fn get_password_safe<T: Device>(
+ device: T,
+ user_pin: &str,
+) -> Result<PasswordSafe<T>, (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<T, C>(
+ device: T,
+ constructor: C,
+ user_pin: &str,
+) -> Result<PasswordSafe<DeviceWrapper>, (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<T: Device> PasswordSafe<T> {
+ /// 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<String, CommandError> {
+ 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<String, CommandError> {
+ 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<String, CommandError> {
+ 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<PasswordSafe<Self>, (Self, CommandError)> {
+ get_password_safe(self, user_pin)
+ }
+}
+
+impl GetPasswordSafe for Storage {
+ fn get_password_safe(self, user_pin: &str) -> Result<PasswordSafe<Self>, (Self, CommandError)> {
+ get_password_safe(self, user_pin)
+ }
+}
+
+impl GetPasswordSafe for DeviceWrapper {
+ fn get_password_safe(self, user_pin: &str) -> Result<PasswordSafe<Self>, (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),
+ }
+ }
+}