~ireas/nitrokey-rs

12fa62483cf45d868099d5d4020333af492eebde — Robin Krahl 2 years ago fe2f398
Introduce into_manager for Device

To enable applications like nitrokey-test to go back to a manager
instance from a Device instance, we add the into_manager function to the
Device trait.  To do that, we have to keep track of the Manager’s
lifetime by adding a lifetime to Device (and then to some other traits
that use Device).
8 files changed, 123 insertions(+), 67 deletions(-)

M CHANGELOG.md
M src/auth.rs
M src/device.rs
M src/error.rs
M src/pws.rs
M tests/device.rs
M tests/otp.rs
M tests/pws.rs
M CHANGELOG.md => CHANGELOG.md +1 -0
@@ 45,6 45,7 @@ SPDX-License-Identifier: MIT
  - Add `ConcurrentAccessError` and `PoisonError` `Error` variants.
  - Add the `Manager` struct that manages connections to Nitrokey devices.
  - Remove `connect`, `connect_model`, `Pro::connect` and `Storage::connect`.
  - Add the `into_manager` function to the `Device` trait.

# v0.3.4 (2019-01-20)
- Fix authentication methods that assumed that `char` is signed.

M src/auth.rs => src/auth.rs +37 -32
@@ 1,6 1,7 @@
// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

use std::marker;
use std::ops;
use std::os::raw::c_char;
use std::os::raw::c_int;


@@ 18,7 19,7 @@ static TEMPORARY_PASSWORD_LENGTH: usize = 25;
/// Provides methods to authenticate as a user or as an admin using a PIN.  The authenticated
/// methods will consume the current device instance.  On success, they return the authenticated
/// device.  Otherwise, they return the current unauthenticated device and the error code.
pub trait Authenticate {
pub trait Authenticate<'a> {
    /// Performs user authentication.  This method consumes the device.  If successful, an
    /// authenticated device is returned.  Otherwise, the current unauthenticated device and the
    /// error are returned.


@@ 61,9 62,9 @@ pub trait Authenticate {
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`RngError`]: enum.CommandError.html#variant.RngError
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, Error)>
    fn authenticate_user(self, password: &str) -> Result<User<'a, Self>, (Self, Error)>
    where
        Self: Device + Sized;
        Self: Device<'a> + Sized;

    /// Performs admin authentication.  This method consumes the device.  If successful, an
    /// authenticated device is returned.  Otherwise, the current unauthenticated device and the


@@ 107,9 108,9 @@ pub trait Authenticate {
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`RngError`]: enum.CommandError.html#variant.RngError
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, Error)>
    fn authenticate_admin(self, password: &str) -> Result<Admin<'a, Self>, (Self, Error)>
    where
        Self: Device + Sized;
        Self: Device<'a> + Sized;
}

trait AuthenticatedDevice<T> {


@@ 128,9 129,10 @@ trait AuthenticatedDevice<T> {
/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
/// [`device`]: #method.device
#[derive(Debug)]
pub struct User<T: Device> {
pub struct User<'a, T: Device<'a>> {
    device: T,
    temp_password: Vec<u8>,
    marker: marker::PhantomData<&'a T>,
}

/// A Nitrokey device with admin authentication.


@@ 143,14 145,15 @@ pub struct User<T: Device> {
/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
/// [`device`]: #method.device
#[derive(Debug)]
pub struct Admin<T: Device> {
pub struct Admin<'a, T: Device<'a>> {
    device: T,
    temp_password: Vec<u8>,
    marker: marker::PhantomData<&'a T>,
}

fn authenticate<D, A, T>(device: D, password: &str, callback: T) -> Result<A, (D, Error)>
fn authenticate<'a, D, A, T>(device: D, password: &str, callback: T) -> Result<A, (D, Error)>
where
    D: Device,
    D: Device<'a>,
    A: AuthenticatedDevice<D>,
    T: Fn(*const c_char, *const c_char) -> c_int,
{


@@ 174,9 177,9 @@ fn authenticate_user_wrapper<'a, T, C>(
    device: T,
    constructor: C,
    password: &str,
) -> Result<User<DeviceWrapper<'a>>, (DeviceWrapper<'a>, Error)>
) -> Result<User<'a, DeviceWrapper<'a>>, (DeviceWrapper<'a>, Error)>
where
    T: Device,
    T: Device<'a> + 'a,
    C: Fn(T) -> DeviceWrapper<'a>,
{
    let result = device.authenticate_user(password);


@@ 190,9 193,9 @@ fn authenticate_admin_wrapper<'a, T, C>(
    device: T,
    constructor: C,
    password: &str,
) -> Result<Admin<DeviceWrapper<'a>>, (DeviceWrapper<'a>, Error)>
) -> Result<Admin<'a, DeviceWrapper<'a>>, (DeviceWrapper<'a>, Error)>
where
    T: Device,
    T: Device<'a> + 'a,
    C: Fn(T) -> DeviceWrapper<'a>,
{
    let result = device.authenticate_admin(password);


@@ 202,7 205,7 @@ where
    }
}

impl<T: Device> User<T> {
impl<'a, T: Device<'a>> User<'a, T> {
    /// Forgets the user authentication and returns an unauthenticated device.  This method
    /// consumes the authenticated device.  It does not perform any actual commands on the
    /// Nitrokey.


@@ 211,7 214,7 @@ impl<T: Device> User<T> {
    }
}

impl<T: Device> ops::Deref for User<T> {
impl<'a, T: Device<'a>> ops::Deref for User<'a, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {


@@ 219,13 222,13 @@ impl<T: Device> ops::Deref for User<T> {
    }
}

impl<T: Device> ops::DerefMut for User<T> {
impl<'a, T: Device<'a>> ops::DerefMut for User<'a, T> {
    fn deref_mut(&mut self) -> &mut T {
        &mut self.device
    }
}

impl<T: Device> GenerateOtp for User<T> {
impl<'a, T: Device<'a>> GenerateOtp for User<'a, T> {
    fn get_hotp_code(&mut self, slot: u8) -> Result<String, Error> {
        result_from_string(unsafe {
            nitrokey_sys::NK_get_hotp_code_PIN(slot, self.temp_password_ptr())


@@ 239,11 242,12 @@ impl<T: Device> GenerateOtp for User<T> {
    }
}

impl<T: Device> AuthenticatedDevice<T> for User<T> {
impl<'a, T: Device<'a>> AuthenticatedDevice<T> for User<'a, T> {
    fn new(device: T, temp_password: Vec<u8>) -> Self {
        User {
            device,
            temp_password,
            marker: marker::PhantomData,
        }
    }



@@ 252,7 256,7 @@ impl<T: Device> AuthenticatedDevice<T> for User<T> {
    }
}

impl<T: Device> ops::Deref for Admin<T> {
impl<'a, T: Device<'a>> ops::Deref for Admin<'a, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {


@@ 260,13 264,13 @@ impl<T: Device> ops::Deref for Admin<T> {
    }
}

impl<T: Device> ops::DerefMut for Admin<T> {
impl<'a, T: Device<'a>> ops::DerefMut for Admin<'a, T> {
    fn deref_mut(&mut self) -> &mut T {
        &mut self.device
    }
}

impl<T: Device> Admin<T> {
impl<'a, T: Device<'a>> Admin<'a, T> {
    /// Forgets the user authentication and returns an unauthenticated device.  This method
    /// consumes the authenticated device.  It does not perform any actual commands on the
    /// Nitrokey.


@@ 316,7 320,7 @@ impl<T: Device> Admin<T> {
    }
}

impl<T: Device> ConfigureOtp for Admin<T> {
impl<'a, T: Device<'a>> ConfigureOtp for Admin<'a, T> {
    fn write_hotp_slot(&mut self, data: OtpSlotData, counter: u64) -> Result<(), Error> {
        let raw_data = RawOtpSlotData::new(data)?;
        get_command_result(unsafe {


@@ 364,11 368,12 @@ impl<T: Device> ConfigureOtp for Admin<T> {
    }
}

impl<T: Device> AuthenticatedDevice<T> for Admin<T> {
impl<'a, T: Device<'a>> AuthenticatedDevice<T> for Admin<'a, T> {
    fn new(device: T, temp_password: Vec<u8>) -> Self {
        Admin {
            device,
            temp_password,
            marker: marker::PhantomData,
        }
    }



@@ 377,8 382,8 @@ impl<T: Device> AuthenticatedDevice<T> for Admin<T> {
    }
}

impl<'a> Authenticate for DeviceWrapper<'a> {
    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, Error)> {
impl<'a> Authenticate<'a> for DeviceWrapper<'a> {
    fn authenticate_user(self, password: &str) -> Result<User<'a, Self>, (Self, Error)> {
        match self {
            DeviceWrapper::Storage(storage) => {
                authenticate_user_wrapper(storage, DeviceWrapper::Storage, password)


@@ 387,7 392,7 @@ impl<'a> Authenticate for DeviceWrapper<'a> {
        }
    }

    fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, Error)> {
    fn authenticate_admin(self, password: &str) -> Result<Admin<'a, Self>, (Self, Error)> {
        match self {
            DeviceWrapper::Storage(storage) => {
                authenticate_admin_wrapper(storage, DeviceWrapper::Storage, password)


@@ 399,28 404,28 @@ impl<'a> Authenticate for DeviceWrapper<'a> {
    }
}

impl<'a> Authenticate for Pro<'a> {
    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, Error)> {
impl<'a> Authenticate<'a> for Pro<'a> {
    fn authenticate_user(self, password: &str) -> Result<User<'a, Self>, (Self, Error)> {
        authenticate(self, password, |password_ptr, temp_password_ptr| unsafe {
            nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr)
        })
    }

    fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, Error)> {
    fn authenticate_admin(self, password: &str) -> Result<Admin<'a, Self>, (Self, Error)> {
        authenticate(self, password, |password_ptr, temp_password_ptr| unsafe {
            nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr)
        })
    }
}

impl<'a> Authenticate for Storage<'a> {
    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, Error)> {
impl<'a> Authenticate<'a> for Storage<'a> {
    fn authenticate_user(self, password: &str) -> Result<User<'a, Self>, (Self, Error)> {
        authenticate(self, password, |password_ptr, temp_password_ptr| unsafe {
            nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr)
        })
    }

    fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, Error)> {
    fn authenticate_admin(self, password: &str) -> Result<Admin<'a, Self>, (Self, Error)> {
        authenticate(self, password, |password_ptr, temp_password_ptr| unsafe {
            nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr)
        })

M src/device.rs => src/device.rs +54 -10
@@ 156,7 156,7 @@ pub enum DeviceWrapper<'a> {
/// [`Pro::connect`]: #method.connect
#[derive(Debug)]
pub struct Pro<'a> {
    manager: &'a mut crate::Manager,
    manager: Option<&'a mut crate::Manager>,
}

/// A Nitrokey Storage device without user or admin authentication.


@@ 200,7 200,7 @@ pub struct Pro<'a> {
/// [`Storage::connect`]: #method.connect
#[derive(Debug)]
pub struct Storage<'a> {
    manager: &'a mut crate::Manager,
    manager: Option<&'a mut crate::Manager>,
}

/// The status of a volume on a Nitrokey Storage device.


@@ 291,7 291,32 @@ pub struct StorageStatus {
///
/// This trait provides the commands that can be executed without authentication and that are
/// present on all supported Nitrokey devices.
pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {
pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt::Debug {
    /// Returns the [`Manager`][] instance that has been used to connect to this device.
    ///
    /// # Example
    ///
    /// ```
    /// use nitrokey::{Device, DeviceWrapper};
    ///
    /// fn do_something(device: DeviceWrapper) {
    ///     // reconnect to any device
    ///     let manager = device.into_manager();
    ///     let device = manager.connect();
    ///     // do something with the device
    ///     // ...
    /// }
    ///
    /// # fn main() -> Result<(), nitrokey::Error> {
    /// match nitrokey::take()?.connect() {
    ///     Ok(device) => do_something(device),
    ///     Err(err) => println!("Could not connect to a Nitrokey: {}", err),
    /// }
    /// #     Ok(())
    /// # }
    /// ```
    fn into_manager(self) -> &'a mut crate::Manager;

    /// Returns the model of the connected Nitrokey device.
    ///
    /// # Example


@@ 657,14 682,14 @@ pub(crate) fn connect_enum(model: Model) -> bool {
}

impl<'a> DeviceWrapper<'a> {
    fn device(&self) -> &dyn Device {
    fn device(&self) -> &dyn Device<'a> {
        match *self {
            DeviceWrapper::Storage(ref storage) => storage,
            DeviceWrapper::Pro(ref pro) => pro,
        }
    }

    fn device_mut(&mut self) -> &mut dyn Device {
    fn device_mut(&mut self) -> &mut dyn Device<'a> {
        match *self {
            DeviceWrapper::Storage(ref mut storage) => storage,
            DeviceWrapper::Pro(ref mut pro) => pro,


@@ 702,7 727,14 @@ impl<'a> GenerateOtp for DeviceWrapper<'a> {
    }
}

impl<'a> Device for DeviceWrapper<'a> {
impl<'a> Device<'a> for DeviceWrapper<'a> {
    fn into_manager(self) -> &'a mut crate::Manager {
        match self {
            DeviceWrapper::Pro(dev) => dev.into_manager(),
            DeviceWrapper::Storage(dev) => dev.into_manager(),
        }
    }

    fn get_model(&self) -> Model {
        match *self {
            DeviceWrapper::Pro(_) => Model::Pro,


@@ 713,7 745,9 @@ impl<'a> Device for DeviceWrapper<'a> {

impl<'a> Pro<'a> {
    pub(crate) fn new(manager: &'a mut crate::Manager) -> Pro<'a> {
        Pro { manager }
        Pro {
            manager: Some(manager),
        }
    }
}



@@ 725,7 759,11 @@ impl<'a> Drop for Pro<'a> {
    }
}

impl<'a> Device for Pro<'a> {
impl<'a> Device<'a> for Pro<'a> {
    fn into_manager(mut self) -> &'a mut crate::Manager {
        self.manager.take().unwrap()
    }

    fn get_model(&self) -> Model {
        Model::Pro
    }


@@ 735,7 773,9 @@ impl<'a> GenerateOtp for Pro<'a> {}

impl<'a> Storage<'a> {
    pub(crate) fn new(manager: &'a mut crate::Manager) -> Storage<'a> {
        Storage { manager }
        Storage {
            manager: Some(manager),
        }
    }

    /// Changes the update PIN.


@@ 1248,7 1288,11 @@ impl<'a> Drop for Storage<'a> {
    }
}

impl<'a> Device for Storage<'a> {
impl<'a> Device<'a> for Storage<'a> {
    fn into_manager(mut self) -> &'a mut crate::Manager {
        self.manager.take().unwrap()
    }

    fn get_model(&self) -> Model {
        Model::Storage
    }

M src/error.rs => src/error.rs +1 -1
@@ 85,7 85,7 @@ impl From<sync::TryLockError<sync::MutexGuard<'static, crate::Manager>>> for Err
    }
}

impl<T: device::Device> From<(T, Error)> for Error {
impl<'a, T: device::Device<'a>> From<(T, Error)> for Error {
    fn from((_, err): (T, Error)) -> Self {
        err
    }

M src/pws.rs => src/pws.rs +15 -15
@@ 57,8 57,8 @@ pub const SLOT_COUNT: u8 = 16;
/// [`lock`]: trait.Device.html#method.lock
/// [`GetPasswordSafe`]: trait.GetPasswordSafe.html
#[derive(Debug)]
pub struct PasswordSafe<'a> {
    _device: &'a dyn Device,
pub struct PasswordSafe<'a, 'b> {
    _device: &'a dyn Device<'b>,
}

/// Provides access to a [`PasswordSafe`][].


@@ 67,7 67,7 @@ pub struct PasswordSafe<'a> {
/// retrieved from it.
///
/// [`PasswordSafe`]: struct.PasswordSafe.html
pub trait GetPasswordSafe {
pub trait GetPasswordSafe<'a> {
    /// Enables and returns the password safe.
    ///
    /// The underlying device must always live at least as long as a password safe retrieved from


@@ 117,13 117,13 @@ pub trait GetPasswordSafe {
    /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
    /// [`Unknown`]: enum.CommandError.html#variant.Unknown
    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
    fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_>, Error>;
    fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_, 'a>, Error>;
}

fn get_password_safe<'a>(
    device: &'a dyn Device,
fn get_password_safe<'a, 'b>(
    device: &'a dyn Device<'b>,
    user_pin: &str,
) -> Result<PasswordSafe<'a>, Error> {
) -> Result<PasswordSafe<'a, 'b>, Error> {
    let user_pin_string = get_cstring(user_pin)?;
    get_command_result(unsafe { nitrokey_sys::NK_enable_password_safe(user_pin_string.as_ptr()) })
        .map(|_| PasswordSafe { _device: device })


@@ 137,7 137,7 @@ fn get_pws_result(s: String) -> Result<String, Error> {
    }
}

impl<'a> PasswordSafe<'a> {
impl<'a, 'b> PasswordSafe<'a, 'b> {
    /// Returns the status of all password slots.
    ///
    /// The status indicates whether a slot is programmed or not.


@@ 357,27 357,27 @@ impl<'a> PasswordSafe<'a> {
    }
}

impl<'a> Drop for PasswordSafe<'a> {
impl<'a, 'b> Drop for PasswordSafe<'a, 'b> {
    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<'a> GetPasswordSafe for Pro<'a> {
    fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_>, Error> {
impl<'a> GetPasswordSafe<'a> for Pro<'a> {
    fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_, 'a>, Error> {
        get_password_safe(self, user_pin)
    }
}

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

impl<'a> GetPasswordSafe for DeviceWrapper<'a> {
    fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_>, Error> {
impl<'a> GetPasswordSafe<'a> for DeviceWrapper<'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 +11 -5
@@ 101,7 101,10 @@ fn get_firmware_version(device: Pro) {
    assert!(version.minor > 0);
}

fn admin_retry<T: Authenticate + Device>(device: T, suffix: &str, count: u8) -> T {
fn admin_retry<'a, T>(device: T, suffix: &str, count: u8) -> T
where
    T: Authenticate<'a> + Device<'a> + 'a,
{
    let result = device.authenticate_admin(&(DEFAULT_ADMIN_PIN.to_owned() + suffix));
    let device = match result {
        Ok(admin) => admin.device(),


@@ 111,7 114,10 @@ fn admin_retry<T: Authenticate + Device>(device: T, suffix: &str, count: u8) -> 
    return device;
}

fn user_retry<T: Authenticate + Device>(device: T, suffix: &str, count: u8) -> T {
fn user_retry<'a, T>(device: T, suffix: &str, count: u8) -> T
where
    T: Authenticate<'a> + Device<'a> + 'a,
{
    let result = device.authenticate_user(&(DEFAULT_USER_PIN.to_owned() + suffix));
    let device = match result {
        Ok(admin) => admin.device(),


@@ 220,10 226,10 @@ fn change_admin_pin(device: DeviceWrapper) {
    device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err();
}

fn require_failed_user_login<D>(device: D, password: &str, error: CommandError) -> D
fn require_failed_user_login<'a, D>(device: D, password: &str, error: CommandError) -> D
where
    D: Device + Authenticate,
    nitrokey::User<D>: std::fmt::Debug,
    D: Device<'a> + Authenticate<'a> + 'a,
    nitrokey::User<'a, D>: std::fmt::Debug,
{
    let result = device.authenticate_user(password);
    assert!(result.is_err());

M tests/otp.rs => tests/otp.rs +2 -2
@@ 36,9 36,9 @@ enum TotpTimestampSize {
    U64,
}

fn make_admin_test_device<T>(device: T) -> Admin<T>
fn make_admin_test_device<'a, T>(device: T) -> Admin<'a, T>
where
    T: Device,
    T: Device<'a>,
    (T, nitrokey::Error): Debug,
{
    unwrap_ok!(device.authenticate_admin(DEFAULT_ADMIN_PIN))

M tests/pws.rs => tests/pws.rs +2 -2
@@ 32,9 32,9 @@ fn get_slot_name_direct(slot: u8) -> Result<String, Error> {
    }
}

fn get_pws<T>(device: &mut T) -> PasswordSafe
fn get_pws<'a, T>(device: &mut T) -> PasswordSafe<'_, 'a>
where
    T: Device,
    T: Device<'a>,
{
    unwrap_ok!(device.get_password_safe(DEFAULT_USER_PIN))
}