~ireas/nitrokey-rs

db48656015d958594b4818582439f7515359cd60 — Robin Krahl 2 months ago b181dd0
Refactor PWS slot access

Previously, the PasswordSafe::get_slot_{name,login,password} methods
would return a SlotNotProgrammed error if the libnitrokey functions
return an empty string.  This was because libnitrokey does not return an
error code when accessing an unprogrammed slot.  But this was ambigous
as the slot values might actually be empty.

With this patch, we deprecate the existing methods for accessing the
slot data.  Instead, we introduce the PasswordSlot struct that can be
used to access the (empty or non-empty) values of programmed slots.
Instances of this struct can be obtained using the get_slots (all
programmed slots), get_slot (one slot that is checked to be programmed)
and get_slot_unchecked (one slot without checking whether it is
programmed) methods.

See this discussion for more information:
	https://github.com/d-e-s-o/nitrocli/issues/133
5 files changed, 285 insertions(+), 45 deletions(-)

M CHANGELOG.md
M src/lib.rs
M src/pws.rs
M tests/device.rs
M tests/pws.rs
M CHANGELOG.md => CHANGELOG.md +9 -0
@@ 3,6 3,15 @@ Copyright (C) 2019-2020 Robin Krahl <robin.krahl@ireas.org>
SPDX-License-Identifier: CC0-1.0
-->

# Unreleased
- Refactor the PWS slot access to clearly distinguish unprogrammed slots and
  empty name, login or password values:
  - Add the `get_slots`, `get_slot` and `get_slot_unchecked` methods to the
    `PasswordSafe` struct and the `PasswordSlot` struct for accessing the PWS
    data.
  - Deprecate the `get_status`, `get_slot_name`, `get_slot_login` and
    `get_slot_password` method of the `PasswordSafe` struct.

# v0.8.0 (2020-09-29)
- Export the `FirmwareVersion` struct.
- Mark the `Error`, `CommandError`, `CommunicationError`, `LibraryError`,

M src/lib.rs => src/lib.rs +1 -1
@@ 143,7 143,7 @@ pub use crate::device::{
};
pub use crate::error::{CommandError, CommunicationError, Error, LibraryError};
pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData};
pub use crate::pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT};
pub use crate::pws::{GetPasswordSafe, PasswordSafe, PasswordSlot, SLOT_COUNT};
pub use crate::util::LogLevel;

use crate::util::{get_cstring, get_last_result};

M src/pws.rs => src/pws.rs +202 -7
@@ 2,7 2,7 @@
// SPDX-License-Identifier: MIT

use crate::device::{Device, DeviceWrapper, Librem, Pro, Storage};
use crate::error::{CommandError, Error};
use crate::error::{CommandError, Error, LibraryError};
use crate::util::{get_command_result, get_cstring, get_last_error, result_from_string};

/// The number of slots in a [`PasswordSafe`][].


@@ 32,10 32,10 @@ pub const SLOT_COUNT: u8 = 16;
/// # use nitrokey::Error;
///
/// fn use_password_safe(pws: &PasswordSafe) -> Result<(), Error> {
///     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);
///     let slot = pws.get_slot(0)?;
///     println!("Credentials for {}:", slot.get_name()?);
///     println!("login:    {}", slot.get_login()?);
///     println!("password: {}", slot.get_password()?);
///     Ok(())
/// }
///


@@ 59,6 59,15 @@ pub struct PasswordSafe<'a, 'b> {
    _device: &'a dyn Device<'b>,
}

/// A slot of a [`PasswordSafe`][].
///
/// [`PasswordSafe`]: struct.PasswordSafe.html
#[derive(Clone, Copy, Debug)]
pub struct PasswordSlot<'p, 'a, 'b> {
    slot: u8,
    _pws: &'p PasswordSafe<'a, 'b>,
}

/// Provides access to a [`PasswordSafe`][].
///
/// The device that implements this trait must always live at least as long as a password safe


@@ 141,10 150,12 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
    ///
    /// The status indicates whether a slot is programmed or not.
    ///
    /// This method is deprecated.  Use [`get_slots`][] instead.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::{GetPasswordSafe, SLOT_COUNT};
    /// use nitrokey::GetPasswordSafe;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {


@@ 161,6 172,9 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`get_slots`]: #method.get_slots
    #[deprecated(since = "0.9.0", note = "Use get_slots() instead")]
    pub fn get_slot_status(&self) -> Result<[bool; SLOT_COUNT as usize], Error> {
        let status_ptr = unsafe { nitrokey_sys::NK_get_password_safe_slot_status() };
        if status_ptr.is_null() {


@@ 178,10 192,143 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
        Ok(result)
    }

    /// Returns all password slots.
    ///
    /// The returned vector contains one element per password slot.  If the slot is not programmed,
    /// the element is `None`.  Otherwise it is a [`PasswordSlot`][] instance for the slot.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::GetPasswordSafe;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// let pws = device.get_password_safe("123456")?;
    /// for (idx, slot) in pws.get_slots()?.iter().enumerate() {
    ///     let status = match *slot {
    ///         Some(_) => "programmed",
    ///         None => "not programmed",
    ///     };
    ///     println!("Slot {}: {}", idx, status);
    /// }
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`PasswordSlot`]: struct.PasswordSlot.html
    pub fn get_slots(&self) -> Result<Vec<Option<PasswordSlot<'_, 'a, 'b>>>, Error> {
        let mut slots = Vec::new();
        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 };
        for slot in 0..SLOT_COUNT {
            if status_array[usize::from(slot)] == 1 {
                slots.push(Some(PasswordSlot { slot, _pws: self }));
            } else {
                slots.push(None);
            }
        }
        unsafe {
            nitrokey_sys::NK_free_password_safe_slot_status(status_ptr);
        }
        Ok(slots)
    }

    /// Returns the slot with the given index if the slot is programmed.
    ///
    /// This method uses [`get_slots`][] to check whether the slot is programmed.  To access the
    /// slot without checking whether it is programmed, use [`get_slot_unchecked`][].
    ///
    /// # 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::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// let pws = device.get_password_safe("123456")?;
    /// let slot = pws.get_slot(0)?;
    /// let name = slot.get_name()?;
    /// let login = slot.get_login()?;
    /// let password = slot.get_password()?;
    /// println!("Credentials for {}: login {}, password {}", name, login, password);
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
    /// [`get_slot_unchecked`]: #method.get_slot_unchecked
    /// [`get_slots`]: #method.get_slots
    pub fn get_slot(&self, slot: u8) -> Result<PasswordSlot<'_, 'a, 'b>, Error> {
        let slot = usize::from(slot);
        let slots = self.get_slots()?;
        if slot < slots.len() {
            slots[slot].ok_or_else(|| CommandError::SlotNotProgrammed.into())
        } else {
            Err(LibraryError::InvalidSlot.into())
        }
    }

    /// Returns the slot with the given index without checking whether it is programmed.
    ///
    /// To check whether a slot is programmed, use [`get_slots`][] or [`get_slot`][].
    ///
    /// # Errors
    ///
    /// - [`InvalidSlot`][] if the given slot is out of range
    ///
    /// # Example
    ///
    /// ```no_run
    /// use nitrokey::GetPasswordSafe;
    /// # use nitrokey::Error;
    ///
    /// # fn try_main() -> Result<(), Error> {
    /// let mut manager = nitrokey::take()?;
    /// let mut device = manager.connect()?;
    /// let pws = device.get_password_safe("123456")?;
    /// let slot = pws.get_slot_unchecked(0)?;
    /// let name = slot.get_name()?;
    /// let login = slot.get_login()?;
    /// let password = slot.get_password()?;
    /// println!("Credentials for {}: login {}, password {}", name, login, password);
    /// #     Ok(())
    /// # }
    /// ```
    ///
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`get_slot`]: #method.get_slot
    /// [`get_slots`]: #method.get_slots
    pub fn get_slot_unchecked(&self, slot: u8) -> Result<PasswordSlot<'_, 'a, 'b>, Error> {
        if slot < SLOT_COUNT {
            Ok(PasswordSlot { slot, _pws: self })
        } else {
            Err(LibraryError::InvalidSlot.into())
        }
    }

    /// Returns the name of the given slot (if it is programmed).
    ///
    /// This method also returns a `SlotNotProgrammed` error if the name is empty.
    ///
    /// This method is deprecated.  Instead, get a [`PasswordSlot`][] instance by calling
    /// [`get_slot`][], [`get_slot_unchecked`][] or [`get_slots`][], and then use
    /// [`PasswordSlot::get_name`][].
    ///
    /// # Errors
    ///
    /// - [`InvalidSlot`][] if the given slot is out of range


@@ 200,7 347,7 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
    ///     Ok(pws) => {
    ///         let name = pws.get_slot_name(0)?;
    ///         let login = pws.get_slot_login(0)?;
    ///         let password = pws.get_slot_login(0)?;
    ///         let password = pws.get_slot_password(0)?;
    ///         println!("Credentials for {}: login {}, password {}", name, login, password);
    ///     },
    ///     Err(err) => eprintln!("Could not open the password safe: {}", err),


@@ 210,7 357,13 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
    /// ```
    ///
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`PasswordSlot`]: struct.PasswordSlot.html
    /// [`PasswordSlot::get_name`]: struct.PasswordSlot.html#method.get_name
    /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
    /// [`get_slot`]: #method.get_slot
    /// [`get_slot_unchecked`]: #method.get_slot_unchecked
    /// [`get_slots`]: #method.get_slots
    #[deprecated(since = "0.9.0", note = "Use get_slot(slot)?.get_name() instead")]
    pub fn get_slot_name(&self, slot: u8) -> Result<String, Error> {
        result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_name(slot) })
            .and_then(get_pws_result)


@@ 220,6 373,10 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
    ///
    /// This method also returns a `SlotNotProgrammed` error if the login is empty.
    ///
    /// This method is deprecated.  Instead, get a [`PasswordSlot`][] instance by calling
    /// [`get_slot`][], [`get_slot_unchecked`][] or [`get_slots`][], and then use
    /// [`PasswordSlot::get_login`][].
    ///
    /// # Errors
    ///
    /// - [`InvalidSlot`][] if the given slot is out of range


@@ 244,7 401,13 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
    /// ```
    ///
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`PasswordSlot`]: struct.PasswordSlot.html
    /// [`PasswordSlot::get_login`]: struct.PasswordSlot.html#method.get_login
    /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
    /// [`get_slot`]: #method.get_slot
    /// [`get_slot_unchecked`]: #method.get_slot_unchecked
    /// [`get_slots`]: #method.get_slots
    #[deprecated(since = "0.9.0", note = "Use get_slot(slot)?.get_login() instead")]
    pub fn get_slot_login(&self, slot: u8) -> Result<String, Error> {
        result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_login(slot) })
            .and_then(get_pws_result)


@@ 254,6 417,10 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
    ///
    /// This method also returns a `SlotNotProgrammed` error if the password is empty.
    ///
    /// This method is deprecated.  Instead, get a [`PasswordSlot`][] instance by calling
    /// [`get_slot`][], [`get_slot_unchecked`][] or [`get_slots`][], and then use
    /// [`PasswordSlot::get_password`][].
    ///
    /// # Errors
    ///
    /// - [`InvalidSlot`][] if the given slot is out of range


@@ 278,7 445,13 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
    /// ```
    ///
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    /// [`PasswordSlot`]: struct.PasswordSlot.html
    /// [`PasswordSlot::get_password`]: struct.PasswordSlot.html#method.get_password
    /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
    /// [`get_slot`]: #method.get_slot
    /// [`get_slot_unchecked`]: #method.get_slot_unchecked
    /// [`get_slots`]: #method.get_slots
    #[deprecated(since = "0.9.0", note = "Use get_slot(slot)?.get_password() instead")]
    pub fn get_slot_password(&self, slot: u8) -> Result<String, Error> {
        result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_password(slot) })
            .and_then(get_pws_result)


@@ 366,6 539,28 @@ impl<'a, 'b> Drop for PasswordSafe<'a, 'b> {
    }
}

impl<'p, 'a, 'b> PasswordSlot<'p, 'a, 'b> {
    /// Returns the index of this PWS slot.
    pub fn index(&self) -> u8 {
        self.slot
    }

    /// Returns the name stored in this PWS slot.
    pub fn get_name(&self) -> Result<String, Error> {
        result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_name(self.slot) })
    }

    /// Returns the login stored in this PWS slot.
    pub fn get_login(&self) -> Result<String, Error> {
        result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_login(self.slot) })
    }

    /// Returns the password stored in this PWS slot.
    pub fn get_password(&self) -> Result<String, Error> {
        result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_password(self.slot) })
    }
}

impl<'a> GetPasswordSafe<'a> for Librem<'a> {
    fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_, 'a>, Error> {
        get_password_safe(self, user_pin)

M tests/device.rs => tests/device.rs +10 -6
@@ 408,9 408,11 @@ fn factory_reset(device: DeviceWrapper) {

    let mut device = user.device();
    let pws = unwrap_ok!(device.get_password_safe(DEFAULT_USER_PIN));
    assert_utf8_err_or_ne("test", pws.get_slot_name(0));
    assert_utf8_err_or_ne("testlogin", pws.get_slot_login(0));
    assert_utf8_err_or_ne("testpw", pws.get_slot_password(0));

    let slot = unwrap_ok!(pws.get_slot_unchecked(0));
    assert_utf8_err_or_ne("test", slot.get_name());
    assert_utf8_err_or_ne("testlogin", slot.get_login());
    assert_utf8_err_or_ne("testpw", slot.get_password());
    drop(pws);

    assert_ok!(3, device.get_user_retry_count());


@@ 436,9 438,11 @@ fn build_aes_key(device: DeviceWrapper) {
        .device();

    let pws = unwrap_ok!(device.get_password_safe(DEFAULT_USER_PIN));
    assert_utf8_err_or_ne("test", pws.get_slot_name(0));
    assert_utf8_err_or_ne("testlogin", pws.get_slot_login(0));
    assert_utf8_err_or_ne("testpw", pws.get_slot_password(0));

    let slot = unwrap_ok!(pws.get_slot_unchecked(0));
    assert_utf8_err_or_ne("test", slot.get_name());
    assert_utf8_err_or_ne("testlogin", slot.get_login());
    assert_utf8_err_or_ne("testpw", slot.get_password());
}

#[test_device]

M tests/pws.rs => tests/pws.rs +63 -31
@@ 7,8 7,8 @@ use std::ffi::CStr;

use libc::{c_int, c_void, free};
use nitrokey::{
    CommandError, Device, Error, GetPasswordSafe, LibraryError, PasswordSafe, DEFAULT_ADMIN_PIN,
    DEFAULT_USER_PIN, SLOT_COUNT,
    CommandError, Device, Error, GetPasswordSafe, LibraryError, PasswordSafe, PasswordSlot,
    DEFAULT_ADMIN_PIN, DEFAULT_USER_PIN, SLOT_COUNT,
};
use nitrokey_sys;
use nitrokey_test::test as test_device;


@@ 60,7 60,9 @@ fn drop(device: DeviceWrapper) {
    {
        let mut pws = get_pws(&mut device);
        assert_ok!((), pws.write_slot(1, "name", "login", "password"));
        assert_ok!("name".to_string(), pws.get_slot_name(1));

        let slot = unwrap_ok!(pws.get_slot_unchecked(1));
        assert_ok!("name".to_string(), slot.get_name());
        let result = get_slot_name_direct(1);
        assert_ok!(String::from("name"), result);
    }


@@ 73,24 75,36 @@ fn drop(device: DeviceWrapper) {

#[test_device]
fn get_status(device: DeviceWrapper) {
    fn get_pws_status(pws: &PasswordSafe<'_, '_>) -> Vec<Option<u8>> {
        unwrap_ok!(pws.get_slots())
            .iter()
            .map(|opt| opt.as_ref().map(PasswordSlot::index))
            .collect()
    }

    let mut device = device;
    let mut pws = get_pws(&mut device);
    for i in 0..SLOT_COUNT {
        assert_ok!((), pws.erase_slot(i));
    }
    let status = unwrap_ok!(pws.get_slot_status());
    assert_eq!(status, [false; SLOT_COUNT as usize]);
    assert_eq!(vec![None; SLOT_COUNT as usize], get_pws_status(&pws));

    assert_ok!((), pws.write_slot(1, "name", "login", "password"));
    let status = unwrap_ok!(pws.get_slot_status());
    for i in 0..SLOT_COUNT {
        assert_eq!(i == 1, status[i as usize]);
    for (i, slot) in get_pws_status(&pws).into_iter().enumerate() {
        if i == 1 {
            assert_eq!(Some(1), slot);
        } else {
            assert_eq!(None, slot);
        }
    }

    for i in 0..SLOT_COUNT {
        assert_ok!((), pws.write_slot(i, "name", "login", "password"));
    }
    assert_ok!([true; SLOT_COUNT as usize], pws.get_slot_status());
    assert_eq!(
        (0..SLOT_COUNT).map(Some).collect::<Vec<_>>(),
        get_pws_status(&pws)
    );
}

#[test_device]


@@ 98,26 112,38 @@ fn get_data(device: DeviceWrapper) {
    let mut device = device;
    let mut pws = get_pws(&mut device);
    assert_ok!((), pws.write_slot(1, "name", "login", "password"));
    assert_ok!("name".to_string(), pws.get_slot_name(1));
    assert_ok!("login".to_string(), pws.get_slot_login(1));
    assert_ok!("password".to_string(), pws.get_slot_password(1));

    let slot = unwrap_ok!(pws.get_slot_unchecked(1));
    assert_ok!("name".to_string(), slot.get_name());
    assert_ok!("login".to_string(), slot.get_login());
    assert_ok!("password".to_string(), slot.get_password());
    drop(slot);

    assert_ok!((), pws.erase_slot(1));
    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot_name(1));
    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot_login(1));
    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot_password(1));

    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot(1));
    let slot = unwrap_ok!(pws.get_slot_unchecked(1));
    assert_ok!(String::new(), slot.get_name());
    assert_ok!(String::new(), slot.get_login());
    assert_ok!(String::new(), slot.get_password());
    drop(slot);

    let name = "with å";
    let login = "pär@test.com";
    let password = "'i3lJc[09?I:,[u7dWz9";
    assert_ok!((), pws.write_slot(1, name, login, password));
    assert_ok!(name.to_string(), pws.get_slot_name(1));
    assert_ok!(login.to_string(), pws.get_slot_login(1));
    assert_ok!(password.to_string(), pws.get_slot_password(1));

    assert_lib_err!(LibraryError::InvalidSlot, pws.get_slot_name(SLOT_COUNT));
    assert_lib_err!(LibraryError::InvalidSlot, pws.get_slot_login(SLOT_COUNT));
    assert_lib_err!(LibraryError::InvalidSlot, pws.get_slot_password(SLOT_COUNT));
    let slot = unwrap_ok!(pws.get_slot_unchecked(1));
    assert_ok!(name.to_string(), slot.get_name());
    assert_ok!(login.to_string(), slot.get_login());
    assert_ok!(password.to_string(), slot.get_password());
    drop(slot);

    assert_lib_err!(LibraryError::InvalidSlot, pws.get_slot(SLOT_COUNT));
    assert_lib_err!(
        LibraryError::InvalidSlot,
        pws.get_slot_unchecked(SLOT_COUNT)
    );
}

#[test_device]


@@ 131,19 157,25 @@ fn write(device: DeviceWrapper) {
    );

    assert_ok!((), pws.write_slot(0, "", "login", "password"));
    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot_name(0));
    assert_ok!(String::from("login"), pws.get_slot_login(0));
    assert_ok!(String::from("password"), pws.get_slot_password(0));

    let slot = unwrap_ok!(pws.get_slot_unchecked(0));
    assert_ok!(String::new(), slot.get_name());
    assert_ok!(String::from("login"), slot.get_login());
    assert_ok!(String::from("password"), slot.get_password());

    assert_ok!((), pws.write_slot(0, "name", "", "password"));
    assert_ok!(String::from("name"), pws.get_slot_name(0));
    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot_login(0));
    assert_ok!(String::from("password"), pws.get_slot_password(0));

    let slot = unwrap_ok!(pws.get_slot_unchecked(0));
    assert_ok!(String::from("name"), slot.get_name());
    assert_ok!(String::new(), slot.get_login());
    assert_ok!(String::from("password"), slot.get_password());

    assert_ok!((), pws.write_slot(0, "name", "login", ""));
    assert_ok!(String::from("name"), pws.get_slot_name(0));
    assert_ok!(String::from("login"), pws.get_slot_login(0));
    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot_password(0));

    let slot = unwrap_ok!(pws.get_slot_unchecked(0));
    assert_ok!(String::from("name"), slot.get_name());
    assert_ok!(String::from("login"), slot.get_login());
    assert_ok!(String::new(), slot.get_password());
}

#[test_device]


@@ 155,5 187,5 @@ fn erase(device: DeviceWrapper) {
    assert_ok!((), pws.write_slot(0, "name", "login", "password"));
    assert_ok!((), pws.erase_slot(0));
    assert_ok!((), pws.erase_slot(0));
    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot_name(0));
    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot(0));
}