~ireas/nitrokey-rs

084b5f4624cffcaff4e63692c133f29de0d8f87d — Robin Krahl 8 months ago a2b7e1e
Support the Librem Key model

This patch adds support for the Librem Key model, a clone of the
Nitrokey Pro.  Functionally, it is identical to the Nitrokey Pro, but it
has a different USB vendor and product ID.

With this patch, we also change the Display implementation for Model to
return the complete name of the model, i. e. Nitrokey Pro, Nitrokey
Storage or Librem Key.
9 files changed, 184 insertions(+), 18 deletions(-)

M CHANGELOG.md
M README.md
M TODO.md
M src/auth.rs
A src/device/librem.rs
M src/device/mod.rs
M src/device/wrapper.rs
M src/lib.rs
M src/pws.rs
M CHANGELOG.md => CHANGELOG.md +5 -0
@@ 20,6 20,11 @@ SPDX-License-Identifier: CC0-1.0
    `nitrokey-sys`.
- Add `get_struct` utility function for querying structs with libnitrokey
  functions that use an output parameter.
- Support the Librem Key model.
  - Add the `Librem` variant to the `Model` and `DeviceWrapper` enums.
  - Add the `Librem` struct.
  - Change the `Display` implementation for the `Model` enum to print the
    complete model name, i. e. Nitrokey Pro, Nitrokey Storage or Librem Key.

# v0.7.1 (2020-08-30)
- Remove the custom `std::error::Error::source` implementation for

M README.md => README.md +3 -0
@@ 77,6 77,9 @@ The test suite contains some test that take very long to execute, for example
filling the SD card of a Nitrokey Storage with random data.  These tests are
ignored per default.  Use `cargo test -- --ignored` to execute the tests.

Currently, there are no tests specifically for the Librem Key as we first have
to release a new `nitrokey-rs` version that `nitrokey-test` can use.

## Acknowledgments

Thanks to Nitrokey UG for providing two Nitrokey devices to support the

M TODO.md => TODO.md +0 -3
@@ 8,8 8,5 @@ SPDX-License-Identifier: CC0-1.0
  issue 65][]).
- Consider only regenerating the null bytes instead of the complete password in
  `util::generate_password`.
- Update to libnitrokey 3.6:
  - New constants:
    - `NK_LIBREM` (`NK_device_model` enum)

[nitrokey-storage-firmware issue 65]: https://github.com/Nitrokey/nitrokey-storage-firmware/issues/65

M src/auth.rs => src/auth.rs +21 -1
@@ 9,7 9,7 @@ use std::os::raw::c_char;
use std::os::raw::c_int;

use crate::config::Config;
use crate::device::{Device, DeviceWrapper, Pro, Storage};
use crate::device::{Device, DeviceWrapper, Librem, Pro, Storage};
use crate::error::Error;
use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData, RawOtpSlotData};
use crate::util::{generate_password, get_command_result, get_cstring, result_from_string};


@@ 371,6 371,9 @@ impl<'a, T: Device<'a>> AuthenticatedDevice<T> for Admin<'a, T> {
impl<'a> Authenticate<'a> for DeviceWrapper<'a> {
    fn authenticate_user(self, password: &str) -> Result<User<'a, Self>, (Self, Error)> {
        match self {
            DeviceWrapper::Librem(librem) => {
                authenticate_user_wrapper(librem, DeviceWrapper::Librem, password)
            }
            DeviceWrapper::Storage(storage) => {
                authenticate_user_wrapper(storage, DeviceWrapper::Storage, password)
            }


@@ 380,6 383,9 @@ impl<'a> Authenticate<'a> for DeviceWrapper<'a> {

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


@@ 390,6 396,20 @@ impl<'a> Authenticate<'a> for DeviceWrapper<'a> {
    }
}

impl<'a> Authenticate<'a> for Librem<'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<'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<'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 {

A src/device/librem.rs => src/device/librem.rs +83 -0
@@ 0,0 1,83 @@
// Copyright (C) 2018-2020 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

use crate::device::{Device, Model, Status};
use crate::error::Error;
use crate::otp::GenerateOtp;
use crate::util::get_struct;

/// A Librem Key device without user or admin authentication.
///
/// Use the [`connect`][] method to obtain an instance wrapper or the [`connect_librem`] method to
/// directly obtain an instance.  If you want to execute a command that requires user or admin
/// authentication, use [`authenticate_admin`][] or [`authenticate_user`][].
///
/// # Examples
///
/// Authentication with error handling:
///
/// ```no_run
/// use nitrokey::{Authenticate, User, Librem};
/// # use nitrokey::Error;
///
/// fn perform_user_task<'a>(device: &User<'a, Librem<'a>>) {}
/// fn perform_other_task(device: &Librem) {}
///
/// # fn try_main() -> Result<(), Error> {
/// let mut manager = nitrokey::take()?;
/// let device = manager.connect_librem()?;
/// let device = match device.authenticate_user("123456") {
///     Ok(user) => {
///         perform_user_task(&user);
///         user.device()
///     },
///     Err((device, err)) => {
///         eprintln!("Could not authenticate as user: {}", err);
///         device
///     },
/// };
/// perform_other_task(&device);
/// #     Ok(())
/// # }
/// ```
///
/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user
/// [`connect`]: struct.Manager.html#method.connect
/// [`connect_librem`]: struct.Manager.html#method.connect_librem
#[derive(Debug)]
pub struct Librem<'a> {
    manager: Option<&'a mut crate::Manager>,
}

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

impl<'a> Drop for Librem<'a> {
    fn drop(&mut self) {
        unsafe {
            nitrokey_sys::NK_logout();
        }
    }
}

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

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

    fn get_status(&self) -> Result<Status, Error> {
        get_struct(|out| unsafe { nitrokey_sys::NK_get_status(out) })
    }
}

impl<'a> GenerateOtp for Librem<'a> {}

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

mod librem;
mod pro;
mod storage;
mod wrapper;


@@ 19,6 20,7 @@ use crate::util::{
    get_command_result, get_cstring, get_struct, owned_str_from_ptr, result_or_error,
};

pub use librem::Librem;
pub use pro::Pro;
pub use storage::{
    OperationStatus, SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode,


@@ 30,6 32,8 @@ pub use wrapper::DeviceWrapper;
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub enum Model {
    /// The Librem Key.
    Librem,
    /// The Nitrokey Storage.
    Storage,
    /// The Nitrokey Pro.


@@ 39,8 43,9 @@ pub enum Model {
impl fmt::Display for Model {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match *self {
            Model::Pro => "Pro",
            Model::Storage => "Storage",
            Model::Librem => "Librem Key",
            Model::Pro => "Nitrokey Pro",
            Model::Storage => "Nitrokey Storage",
        })
    }
}


@@ 48,6 53,7 @@ impl fmt::Display for Model {
impl From<Model> for nitrokey_sys::NK_device_model {
    fn from(model: Model) -> Self {
        match model {
            Model::Librem => nitrokey_sys::NK_device_model_NK_LIBREM,
            Model::Storage => nitrokey_sys::NK_device_model_NK_STORAGE,
            Model::Pro => nitrokey_sys::NK_device_model_NK_PRO,
        }


@@ 62,6 68,7 @@ impl TryFrom<nitrokey_sys::NK_device_model> for Model {
            nitrokey_sys::NK_device_model_NK_DISCONNECTED => {
                Err(CommunicationError::NotConnected.into())
            }
            nitrokey_sys::NK_device_model_NK_LIBREM => Ok(Model::Librem),
            nitrokey_sys::NK_device_model_NK_PRO => Ok(Model::Pro),
            nitrokey_sys::NK_device_model_NK_STORAGE => Ok(Model::Storage),
            _ => Err(Error::UnsupportedModelError),


@@ 195,8 202,8 @@ impl TryFrom<&nitrokey_sys::NK_device_info> for DeviceInfo {
impl fmt::Display for DeviceInfo {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self.model {
            Some(model) => write!(f, "Nitrokey {}", model)?,
            None => write!(f, "Unsupported Nitrokey model")?,
            Some(model) => f.write_str(&model.to_string())?,
            None => f.write_str("Unsupported Nitrokey model")?,
        }
        write!(f, " at {} with ", self.path)?;
        match self.serial_number {


@@ 688,6 695,7 @@ pub(crate) fn create_device_wrapper(
    model: Model,
) -> DeviceWrapper<'_> {
    match model {
        Model::Librem => Librem::new(manager).into(),
        Model::Pro => Pro::new(manager).into(),
        Model::Storage => Storage::new(manager).into(),
    }

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

use crate::device::{Device, Model, Pro, Status, Storage};
use crate::device::{Device, Librem, Model, Pro, Status, Storage};
use crate::error::Error;
use crate::otp::GenerateOtp;



@@ 66,6 66,8 @@ use crate::otp::GenerateOtp;
#[derive(Debug)]
#[non_exhaustive]
pub enum DeviceWrapper<'a> {
    /// A Librem Key device.
    Librem(Librem<'a>),
    /// A Nitrokey Storage device.
    Storage(Storage<'a>),
    /// A Nitrokey Pro device.


@@ 75,6 77,7 @@ pub enum DeviceWrapper<'a> {
impl<'a> DeviceWrapper<'a> {
    fn device(&self) -> &dyn Device<'a> {
        match *self {
            DeviceWrapper::Librem(ref librem) => librem,
            DeviceWrapper::Storage(ref storage) => storage,
            DeviceWrapper::Pro(ref pro) => pro,
        }


@@ 82,12 85,19 @@ impl<'a> DeviceWrapper<'a> {

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

impl<'a> From<Librem<'a>> for DeviceWrapper<'a> {
    fn from(device: Librem<'a>) -> Self {
        DeviceWrapper::Librem(device)
    }
}

impl<'a> From<Pro<'a>> for DeviceWrapper<'a> {
    fn from(device: Pro<'a>) -> Self {
        DeviceWrapper::Pro(device)


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


@@ 128,6 139,7 @@ impl<'a> Device<'a> for DeviceWrapper<'a> {

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


@@ 135,6 147,7 @@ impl<'a> Device<'a> for DeviceWrapper<'a> {

    fn get_status(&self) -> Result<Status, Error> {
        match self {
            DeviceWrapper::Librem(dev) => dev.get_status(),
            DeviceWrapper::Pro(dev) => dev.get_status(),
            DeviceWrapper::Storage(dev) => dev.get_status(),
        }

M src/lib.rs => src/lib.rs +39 -8
@@ 13,8 13,8 @@
//! an reference to the [`Manager`][] singleton that keeps track of the connections.  Then use the
//! [`connect`][] method to connect to any Nitrokey device.  The method will return a
//! [`DeviceWrapper`][] that abstracts over the supported Nitrokey devices.  You can also use
//! [`connect_model`][], [`connect_pro`][] or [`connect_storage`][] to connect to a specific
//! device.
//! [`connect_model`][], [`connect_librem`][], [`connect_pro`][] or [`connect_storage`][] to
//! connect to a specific device.
//!
//! To get a list of all connected Nitrokey devices, use the [`list_devices`][] function.  You can
//! then connect to one of the connected devices using the [`connect_path`][] function of the


@@ 98,6 98,7 @@
//! [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user
//! [`take`]: fn.take.html
//! [`connect`]: struct.Manager.html#method.connect
//! [`connect_librem`]: struct.Manager.html#method.connect_librem
//! [`connect_model`]: struct.Manager.html#method.connect_model
//! [`connect_path`]: struct.Manager.html#method.connect_path
//! [`connect_pro`]: struct.Manager.html#method.connect_pro


@@ 136,8 137,9 @@ use std::sync;
pub use crate::auth::{Admin, Authenticate, User};
pub use crate::config::Config;
pub use crate::device::{
    Device, DeviceInfo, DeviceWrapper, FirmwareVersion, Model, OperationStatus, Pro, SdCardData,
    SerialNumber, Status, Storage, StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus,
    Device, DeviceInfo, DeviceWrapper, FirmwareVersion, Librem, Model, OperationStatus, Pro,
    SdCardData, SerialNumber, Status, Storage, StorageProductionInfo, StorageStatus, VolumeMode,
    VolumeStatus,
};
pub use crate::error::{CommandError, CommunicationError, Error, LibraryError};
pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData};


@@ 192,8 194,8 @@ impl fmt::Display for Version {
/// time.
///
/// To obtain a reference to an instance of this manager, use the [`take`][] function.  Use one of
/// the connect methods – [`connect`][], [`connect_model`][], [`connect_pro`][] or
/// [`connect_storage`][] – to retrieve a [`Device`][] instance.
/// the connect methods – [`connect`][], [`connect_model`][], [`connect_librem`][],
/// [`connect_pro`][] or [`connect_storage`][] – to retrieve a [`Device`][] instance.
///
/// # Examples
///


@@ 229,6 231,7 @@ impl fmt::Display for Version {
/// ```
///
/// [`connect`]: #method.connect
/// [`connect_librem`]: #method.connect_librem
/// [`connect_model`]: #method.connect_model
/// [`connect_pro`]: #method.connect_pro
/// [`connect_storage`]: #method.connect_storage


@@ 249,8 252,7 @@ impl Manager {

    /// Connects to a Nitrokey device.
    ///
    /// This method can be used to connect to any connected device, both a Nitrokey Pro and a
    /// Nitrokey Storage.
    /// This method can be used to connect to any connected device.
    ///
    /// # Errors
    ///


@@ 355,6 357,35 @@ impl Manager {
        }
    }

    /// Connects to a Librem Key.
    ///
    /// # Errors
    ///
    /// - [`NotConnected`][] if no Nitrokey device of the given model is connected
    ///
    /// # Example
    ///
    /// ```
    /// use nitrokey::Librem;
    ///
    /// fn use_librem(device: Librem) {}
    ///
    /// match nitrokey::take()?.connect_librem() {
    ///     Ok(device) => use_librem(device),
    ///     Err(err) => println!("Could not connect to the Librem Key: {}", err),
    /// }
    /// # Ok::<(), nitrokey::Error>(())
    /// ```
    ///
    /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
    pub fn connect_librem(&mut self) -> Result<Librem<'_>, Error> {
        if device::connect_enum(device::Model::Librem) {
            Ok(device::Librem::new(self))
        } else {
            Err(CommunicationError::NotConnected.into())
        }
    }

    /// Connects to a Nitrokey Pro.
    ///
    /// # Errors

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

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



@@ 369,6 369,12 @@ impl<'a, 'b> Drop for PasswordSafe<'a, 'b> {
    }
}

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)
    }
}

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)