aboutsummaryrefslogtreecommitdiff
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
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.
-rw-r--r--TODO.md9
-rw-r--r--src/device.rs3
-rw-r--r--src/lib.rs2
-rw-r--r--src/pws.rs403
-rw-r--r--src/tests/mod.rs1
-rw-r--r--src/tests/otp.rs7
-rw-r--r--src/tests/pws.rs169
7 files changed, 583 insertions, 11 deletions
diff --git a/TODO.md b/TODO.md
index 9b9bc72..dba39f8 100644
--- a/TODO.md
+++ b/TODO.md
@@ -4,13 +4,6 @@
- `NK_factory_reset`
- `NK_build_aes_key`
- `NK_unlock_user_password`
- - `NK_enable_password_safe`
- - `NK_get_password_safe_slot_status`
- - `NK_get_password_safe_slot_name`
- - `NK_get_password_safe_slot_login`
- - `NK_get_password_safe_slot_password`
- - `NK_write_password_safe_slot`
- - `NK_erase_password_safe_slot`
- `NK_is_AES_supported`
- `NK_send_startup`
- `NK_unlock_encrypted_volume`
@@ -42,3 +35,5 @@
- Prevent construction of internal types.
- Add Storage-only examples to the `DeviceWrapper` documentation.
- Fix generic connection (`get_connected_device`).
+- More specific error checking in the tests.
+- Differentiate empty strings and errors (see `result_from_string`).
diff --git a/src/device.rs b/src/device.rs
index 1201540..7d69df8 100644
--- a/src/device.rs
+++ b/src/device.rs
@@ -3,6 +3,7 @@ use libc;
use nitrokey_sys;
use std::ffi::CString;
use otp::GenerateOtp;
+use pws::GetPasswordSafe;
use util::{get_last_error, result_from_string, CommandError, CommandStatus};
/// Available Nitrokey models.
@@ -147,7 +148,7 @@ pub struct Storage {}
///
/// This trait provides the commands that can be executed without authentication and that are
/// present on all supported Nitrokey devices.
-pub trait Device: GenerateOtp {
+pub trait Device: GetPasswordSafe + GenerateOtp {
/// Returns the serial number of the Nitrokey device. The serial number is the string
/// representation of a hex number.
///
diff --git a/src/lib.rs b/src/lib.rs
index 4d5452d..b30e9ba 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -92,6 +92,7 @@ mod auth;
mod config;
mod device;
mod otp;
+mod pws;
mod util;
#[cfg(test)]
mod tests;
@@ -100,6 +101,7 @@ pub use config::Config;
pub use device::{connect, Device, DeviceWrapper, Pro, Storage};
pub use auth::{Admin, Authenticate, User};
pub use otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData};
+pub use pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT};
pub use util::{CommandError, CommandStatus, LogLevel};
/// Enables or disables debug output. Calling this method with `true` is equivalent to setting the
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),
+ }
+ }
+}
diff --git a/src/tests/mod.rs b/src/tests/mod.rs
index c2c9f9d..34ca0aa 100644
--- a/src/tests/mod.rs
+++ b/src/tests/mod.rs
@@ -1,3 +1,4 @@
mod device;
mod otp;
+mod pws;
mod util;
diff --git a/src/tests/otp.rs b/src/tests/otp.rs
index 10f569d..8c59341 100644
--- a/src/tests/otp.rs
+++ b/src/tests/otp.rs
@@ -1,6 +1,6 @@
use std::ops::Deref;
-use {Admin, Authenticate, CommandError, CommandStatus, Config, ConfigureOtp, GenerateOtp,
- OtpMode, OtpSlotData};
+use {Admin, Authenticate, CommandError, CommandStatus, Config, ConfigureOtp, GenerateOtp, OtpMode,
+ OtpSlotData};
use tests::util::{Target, ADMIN_PASSWORD, USER_PASSWORD};
// test suite according to RFC 4226, Appendix D
@@ -22,7 +22,8 @@ static TOTP_CODES: &[(u64, &str)] = &[
];
fn get_admin_test_device() -> Admin<Target> {
- Target::connect().expect("Could not connect to the Nitrokey Pro.")
+ Target::connect()
+ .expect("Could not connect to the Nitrokey Pro.")
.authenticate_admin(ADMIN_PASSWORD)
.expect("Could not login as admin.")
}
diff --git a/src/tests/pws.rs b/src/tests/pws.rs
new file mode 100644
index 0000000..5f5a325
--- /dev/null
+++ b/src/tests/pws.rs
@@ -0,0 +1,169 @@
+use util::{CommandError, CommandStatus};
+use pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT};
+use tests::util::{Target, ADMIN_PASSWORD, USER_PASSWORD};
+
+fn get_pws() -> PasswordSafe<Target> {
+ Target::connect()
+ .unwrap()
+ .get_password_safe(USER_PASSWORD)
+ .unwrap()
+}
+
+#[test]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
+fn enable() {
+ assert!(
+ Target::connect()
+ .unwrap()
+ .get_password_safe(&(USER_PASSWORD.to_owned() + "123"))
+ .is_err()
+ );
+ assert!(
+ Target::connect()
+ .unwrap()
+ .get_password_safe(USER_PASSWORD)
+ .is_ok()
+ );
+ assert!(
+ Target::connect()
+ .unwrap()
+ .get_password_safe(ADMIN_PASSWORD)
+ .is_err()
+ );
+ assert!(
+ Target::connect()
+ .unwrap()
+ .get_password_safe(USER_PASSWORD)
+ .is_ok()
+ );
+}
+
+#[test]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
+fn get_status() {
+ let pws = get_pws();
+ for i in 0..SLOT_COUNT {
+ assert_eq!(
+ CommandStatus::Success,
+ pws.erase_slot(i),
+ "Could not erase slot {}",
+ i
+ );
+ }
+ let status = pws.get_slot_status().unwrap();
+ assert_eq!(status, [false; SLOT_COUNT as usize]);
+
+ assert_eq!(
+ CommandStatus::Success,
+ pws.write_slot(1, "name", "login", "password")
+ );
+ let status = pws.get_slot_status().unwrap();
+ for i in 0..SLOT_COUNT {
+ assert_eq!(i == 1, status[i as usize]);
+ }
+
+ for i in 0..SLOT_COUNT {
+ assert_eq!(
+ CommandStatus::Success,
+ pws.write_slot(i, "name", "login", "password")
+ );
+ }
+ let status = pws.get_slot_status().unwrap();
+ assert_eq!(status, [true; SLOT_COUNT as usize]);
+}
+
+#[test]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
+fn get_data() {
+ let pws = get_pws();
+ assert_eq!(
+ CommandStatus::Success,
+ pws.write_slot(1, "name", "login", "password")
+ );
+ assert_eq!("name", pws.get_slot_name(1).unwrap());
+ assert_eq!("login", pws.get_slot_login(1).unwrap());
+ assert_eq!("password", pws.get_slot_password(1).unwrap());
+
+ assert_eq!(CommandStatus::Success, pws.erase_slot(1));
+ // TODO: check error codes
+ assert_eq!(Err(CommandError::Unknown), pws.get_slot_name(1));
+ assert_eq!(Err(CommandError::Unknown), pws.get_slot_login(1));
+ assert_eq!(Err(CommandError::Unknown), pws.get_slot_password(1));
+
+ let name = "with å";
+ let login = "pär@test.com";
+ let password = "'i3lJc[09?I:,[u7dWz9";
+ assert_eq!(
+ CommandStatus::Success,
+ pws.write_slot(1, name, login, password)
+ );
+ assert_eq!(name, pws.get_slot_name(1).unwrap());
+ assert_eq!(login, pws.get_slot_login(1).unwrap());
+ assert_eq!(password, pws.get_slot_password(1).unwrap());
+
+ assert_eq!(
+ Err(CommandError::InvalidSlot),
+ pws.get_slot_name(SLOT_COUNT)
+ );
+ assert_eq!(
+ Err(CommandError::InvalidSlot),
+ pws.get_slot_login(SLOT_COUNT)
+ );
+ assert_eq!(
+ Err(CommandError::InvalidSlot),
+ pws.get_slot_password(SLOT_COUNT)
+ );
+}
+
+#[test]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
+fn write() {
+ let pws = get_pws();
+
+ assert_eq!(
+ CommandStatus::Error(CommandError::InvalidSlot),
+ pws.write_slot(SLOT_COUNT, "name", "login", "password")
+ );
+
+ assert_eq!(
+ CommandStatus::Success,
+ pws.write_slot(0, "", "login", "password")
+ );
+ assert_eq!(Err(CommandError::Unknown), pws.get_slot_name(0));
+ assert_eq!(Ok(String::from("login")), pws.get_slot_login(0));
+ assert_eq!(Ok(String::from("password")), pws.get_slot_password(0));
+
+ assert_eq!(
+ CommandStatus::Success,
+ pws.write_slot(0, "name", "", "password")
+ );
+ assert_eq!(Ok(String::from("name")), pws.get_slot_name(0));
+ assert_eq!(Err(CommandError::Unknown), pws.get_slot_login(0));
+ assert_eq!(Ok(String::from("password")), pws.get_slot_password(0));
+
+ assert_eq!(
+ CommandStatus::Success,
+ pws.write_slot(0, "name", "login", "")
+ );
+ assert_eq!(Ok(String::from("name")), pws.get_slot_name(0));
+ assert_eq!(Ok(String::from("login")), pws.get_slot_login(0));
+ assert_eq!(Err(CommandError::Unknown), pws.get_slot_password(0));
+}
+
+#[test]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
+fn erase() {
+ let pws = get_pws();
+ assert_eq!(
+ CommandStatus::Error(CommandError::InvalidSlot),
+ pws.erase_slot(SLOT_COUNT)
+ );
+
+ assert_eq!(
+ CommandStatus::Success,
+ pws.write_slot(0, "name", "login", "password")
+ );
+ assert_eq!(CommandStatus::Success, pws.erase_slot(0));
+ assert_eq!(CommandStatus::Success, pws.erase_slot(0));
+ assert_eq!(Err(CommandError::Unknown), pws.get_slot_name(0));
+}