~ireas/nitrokey-rs

8afd50e9ab9022c813a709e05bde5f60843401dc — Robin Krahl 3 months ago 6a94c70 + 4c3b7f9
Merge branch 'nitrokey-sys-v3.6.0' into next

This patch series updates the nitrokey-sys dependency to version 3.6.0
and makes use of the new features.
M CHANGELOG.md => CHANGELOG.md +15 -0
@@ 10,6 10,21 @@ SPDX-License-Identifier: CC0-1.0
  - Bump the MSRV to 1.40.0.
- Rename the `numlock`, `capslock`, `scrollock` fields of the `Config` struct
  to `num_lock`, `caps_lock`, `scroll_lock`.
- Update the `nitrokey-sys` dependency to v3.6.0.
  - Use `NK_device_serial_number_as_u32` instead of `NK_device_serial_number`
    in `Device::get_serial_number`.
  - Use `NK_free_password_safe_slot_status` to free the pointer returned by
    `NK_get_password_safe_slot_status` in `PasswordSafe::get_slot_status`.
  - Use the `NK_config` struct for configuration handling.
  - Use the derived `Default` implementation for the structs defined by
    `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 Cargo.toml => Cargo.toml +1 -1
@@ 19,7 19,7 @@ exclude = [".builds/*"]
[dependencies]
lazy_static = "1.2"
libc = "0.2"
nitrokey-sys = "3.5"
nitrokey-sys = "3.6"
rand_core = {version = "0.5.1", features = ["getrandom"] }

[dev-dependencies]

M README.md => README.md +8 -6
@@ 46,12 46,11 @@ supported by `nitrokey-rs`:
- `NK_is_AES_supported`.  This function is no longer needed for Nitrokey
  devices with a recent firmware version.
- `NK_send_startup`.  Currently, this function is redundant to `NK_get_time`.
- `NK_set_unencrypted_volume_rorw_pin_type_user`,
  `NK_set_unencrypted_read_only`, `NK_set_unencrypted_read_write`.  These
  functions are only relevant for older firmware versions (pre-v0.51).  As the
  Nitrokey Storage firmware can be updated easily, we do not support these
  outdated versions.
- `NK_totp_get_time`, `NK_status`.  These functions are deprecated.
- `NK_set_unencrypted_volume_rorw_pin_type_user`: This function is only
  relevant for older firmware versions (pre-v0.51).  As the Nitrokey Storage
  firmware can be updated easily, we do not support these outdated versions.
- `NK_totp_get_time`, `NK_status`, `NK_set_unencrypted_read_only`,
  `NK_set_unencrypted_read_write`.  These functions are deprecated.
- `NK_read_HOTP_slot`.  This function is only available for HOTP slots, not for
  TOTP.  We will support it once both types are supported by `libnitrokey`.
- All `*_as_string` functions that return string representations of data


@@ 77,6 76,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 src/auth.rs => src/auth.rs +24 -12
@@ 1,15 1,15 @@
// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

use std::convert::TryFrom as _;
use std::convert::TryInto as _;
use std::ffi::CString;
use std::marker;
use std::ops;
use std::os::raw::c_char;
use std::os::raw::c_int;

use crate::config::{Config, RawConfig};
use crate::device::{Device, DeviceWrapper, Pro, Storage};
use crate::config::Config;
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};


@@ 304,16 304,8 @@ impl<'a, T: Device<'a>> Admin<'a, T> {
    ///
    /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
    pub fn write_config(&mut self, config: Config) -> Result<(), Error> {
        let raw_config = RawConfig::try_from(config)?;
        get_command_result(unsafe {
            nitrokey_sys::NK_write_config(
                raw_config.num_lock,
                raw_config.caps_lock,
                raw_config.scroll_lock,
                raw_config.user_password,
                false,
                self.temp_password.as_ptr(),
            )
            nitrokey_sys::NK_write_config_struct(config.try_into()?, self.temp_password.as_ptr())
        })
    }
}


@@ 379,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)
            }


@@ 388,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)
            }


@@ 398,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 {

M src/config.rs => src/config.rs +33 -31
@@ 25,14 25,6 @@ pub struct Config {
    pub user_password: bool,
}

#[derive(Debug)]
pub struct RawConfig {
    pub num_lock: u8,
    pub caps_lock: u8,
    pub scroll_lock: u8,
    pub user_password: bool,
}

fn config_otp_slot_to_option(value: u8) -> Option<u8> {
    if value < 3 {
        Some(value)


@@ 68,39 60,49 @@ impl Config {
            user_password,
        }
    }

    fn from_raw(numlock: u8, capslock: u8, scrollock: u8, user_password: bool) -> Config {
        Config {
            num_lock: config_otp_slot_to_option(numlock),
            caps_lock: config_otp_slot_to_option(capslock),
            scroll_lock: config_otp_slot_to_option(scrollock),
            user_password,
        }
    }
}

impl convert::TryFrom<Config> for RawConfig {
impl convert::TryFrom<Config> for nitrokey_sys::NK_config {
    type Error = Error;

    fn try_from(config: Config) -> Result<RawConfig, Error> {
        Ok(RawConfig {
            num_lock: option_to_config_otp_slot(config.num_lock)?,
            caps_lock: option_to_config_otp_slot(config.caps_lock)?,
            scroll_lock: option_to_config_otp_slot(config.scroll_lock)?,
            user_password: config.user_password,
    fn try_from(config: Config) -> Result<nitrokey_sys::NK_config, Error> {
        Ok(nitrokey_sys::NK_config {
            numlock: option_to_config_otp_slot(config.num_lock)?,
            capslock: option_to_config_otp_slot(config.caps_lock)?,
            scrolllock: option_to_config_otp_slot(config.scroll_lock)?,
            enable_user_password: config.user_password,
            disable_user_password: false,
        })
    }
}

impl From<&nitrokey_sys::NK_status> for RawConfig {
    fn from(status: &nitrokey_sys::NK_status) -> Self {
        Self {
            num_lock: status.config_numlock,
            caps_lock: status.config_capslock,
            scroll_lock: status.config_scrolllock,
            user_password: status.otp_user_password,
        }
impl From<nitrokey_sys::NK_config> for Config {
    fn from(config: nitrokey_sys::NK_config) -> Config {
        Config::from_raw(
            config.numlock,
            config.capslock,
            config.scrolllock,
            config.enable_user_password,
        )
    }
}

impl Into<Config> for RawConfig {
    fn into(self) -> Config {
        Config {
            num_lock: config_otp_slot_to_option(self.num_lock),
            caps_lock: config_otp_slot_to_option(self.caps_lock),
            scroll_lock: config_otp_slot_to_option(self.scroll_lock),
            user_password: self.user_password,
        }
impl From<&nitrokey_sys::NK_status> for Config {
    fn from(status: &nitrokey_sys::NK_status) -> Config {
        Config::from_raw(
            status.config_numlock,
            status.config_capslock,
            status.config_scrolllock,
            status.otp_user_password,
        )
    }
}

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 +18 -21
@@ 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;


@@ 11,14 12,15 @@ use std::fmt;
use std::str;

use crate::auth::Authenticate;
use crate::config::{Config, RawConfig};
use crate::config::Config;
use crate::error::{CommunicationError, Error, LibraryError};
use crate::otp::GenerateOtp;
use crate::pws::GetPasswordSafe;
use crate::util::{
    get_command_result, get_cstring, owned_str_from_ptr, result_or_error, run_with_string,
    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 {


@@ 274,7 281,7 @@ impl From<nitrokey_sys::NK_status> for Status {
                minor: status.firmware_version_minor,
            },
            serial_number: SerialNumber::new(status.serial_number_smart_card),
            config: RawConfig::from(&status).into(),
            config: Config::from(&status),
        }
    }
}


@@ 375,9 382,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
    /// # }
    /// ```
    fn get_serial_number(&self) -> Result<SerialNumber, Error> {
        run_with_string(unsafe { nitrokey_sys::NK_device_serial_number() }, |s| {
            s.parse()
        })
        let serial_number = unsafe { nitrokey_sys::NK_device_serial_number_as_u32() };
        result_or_error(SerialNumber::new(serial_number))
    }

    /// Returns the number of remaining authentication attempts for the user.  The total number of


@@ 472,17 478,7 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
    /// # }
    /// ```
    fn get_config(&self) -> Result<Config, Error> {
        let mut raw_status = nitrokey_sys::NK_status {
            firmware_version_major: 0,
            firmware_version_minor: 0,
            serial_number_smart_card: 0,
            config_numlock: 0,
            config_capslock: 0,
            config_scrolllock: 0,
            otp_user_password: false,
        };
        get_command_result(unsafe { nitrokey_sys::NK_get_status(&mut raw_status) })?;
        Ok(RawConfig::from(&raw_status).into())
        get_struct(|out| unsafe { nitrokey_sys::NK_read_config_struct(out) })
    }

    /// Changes the administrator PIN.


@@ 699,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/pro.rs => src/device/pro.rs +2 -12
@@ 4,7 4,7 @@
use crate::device::{Device, Model, Status};
use crate::error::Error;
use crate::otp::GenerateOtp;
use crate::util::get_command_result;
use crate::util::get_struct;

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


@@ 76,17 76,7 @@ impl<'a> Device<'a> for Pro<'a> {
    }

    fn get_status(&self) -> Result<Status, Error> {
        let mut raw_status = nitrokey_sys::NK_status {
            firmware_version_major: 0,
            firmware_version_minor: 0,
            serial_number_smart_card: 0,
            config_numlock: 0,
            config_capslock: 0,
            config_scrolllock: 0,
            otp_user_password: false,
        };
        get_command_result(unsafe { nitrokey_sys::NK_get_status(&mut raw_status) })?;
        Ok(raw_status.into())
        get_struct(|out| unsafe { nitrokey_sys::NK_get_status(out) })
    }
}


M src/device/storage.rs => src/device/storage.rs +5 -53
@@ 8,7 8,7 @@ use std::ops;
use crate::device::{Device, FirmwareVersion, Model, SerialNumber, Status};
use crate::error::{CommandError, Error};
use crate::otp::GenerateOtp;
use crate::util::{get_command_result, get_cstring, get_last_error};
use crate::util::{get_command_result, get_cstring, get_last_error, get_struct};

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


@@ 549,26 549,7 @@ impl<'a> Storage<'a> {
    /// # }
    /// ```
    pub fn get_storage_status(&self) -> Result<StorageStatus, Error> {
        let mut raw_status = nitrokey_sys::NK_storage_status {
            unencrypted_volume_read_only: false,
            unencrypted_volume_active: false,
            encrypted_volume_read_only: false,
            encrypted_volume_active: false,
            hidden_volume_read_only: false,
            hidden_volume_active: false,
            firmware_version_major: 0,
            firmware_version_minor: 0,
            firmware_locked: false,
            serial_number_sd_card: 0,
            serial_number_smart_card: 0,
            user_retry_count: 0,
            admin_retry_count: 0,
            new_sd_card_found: false,
            filled_with_random: false,
            stick_initialized: false,
        };
        let raw_result = unsafe { nitrokey_sys::NK_get_status_storage(&mut raw_status) };
        get_command_result(raw_result).map(|_| StorageStatus::from(raw_status))
        get_struct(|out| unsafe { nitrokey_sys::NK_get_status_storage(out) })
    }

    /// Returns the production information for the connected storage device.


@@ 594,23 575,7 @@ impl<'a> Storage<'a> {
    /// # }
    /// ```
    pub fn get_production_info(&self) -> Result<StorageProductionInfo, Error> {
        let mut raw_data = nitrokey_sys::NK_storage_ProductionTest {
            FirmwareVersion_au8: [0, 2],
            FirmwareVersionInternal_u8: 0,
            SD_Card_Size_u8: 0,
            CPU_CardID_u32: 0,
            SmartCardID_u32: 0,
            SD_CardID_u32: 0,
            SC_UserPwRetryCount: 0,
            SC_AdminPwRetryCount: 0,
            SD_Card_ManufacturingYear_u8: 0,
            SD_Card_ManufacturingMonth_u8: 0,
            SD_Card_OEM_u16: 0,
            SD_WriteSpeed_u16: 0,
            SD_Card_Manufacturer_u8: 0,
        };
        let raw_result = unsafe { nitrokey_sys::NK_get_storage_production_info(&mut raw_data) };
        get_command_result(raw_result).map(|_| StorageProductionInfo::from(raw_data))
        get_struct(|out| unsafe { nitrokey_sys::NK_get_storage_production_info(out) })
    }

    /// Clears the warning for a new SD card.


@@ 666,10 631,7 @@ impl<'a> Storage<'a> {
    /// # Ok::<(), nitrokey::Error>(())
    /// ```
    pub fn get_sd_card_usage(&self) -> Result<ops::Range<u8>, Error> {
        let mut usage_data = nitrokey_sys::NK_SD_usage_data {
            write_level_min: 0,
            write_level_max: 0,
        };
        let mut usage_data = nitrokey_sys::NK_SD_usage_data::default();
        let result = unsafe { nitrokey_sys::NK_get_SD_usage_data(&mut usage_data) };
        match get_command_result(result) {
            Ok(_) => {


@@ 811,17 773,7 @@ impl<'a> Device<'a> for Storage<'a> {
        // [0] https://github.com/Nitrokey/nitrokey-storage-firmware/issues/96
        // [1] https://github.com/Nitrokey/libnitrokey/issues/166

        let mut raw_status = nitrokey_sys::NK_status {
            firmware_version_major: 0,
            firmware_version_minor: 0,
            serial_number_smart_card: 0,
            config_numlock: 0,
            config_capslock: 0,
            config_scrolllock: 0,
            otp_user_password: false,
        };
        get_command_result(unsafe { nitrokey_sys::NK_get_status(&mut raw_status) })?;
        let mut status = Status::from(raw_status);
        let mut status: Status = get_struct(|out| unsafe { nitrokey_sys::NK_get_status(out) })?;

        let storage_status = self.get_storage_status()?;
        status.firmware_version = storage_status.firmware_version;

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 +8 -2
@@ 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};



@@ 173,7 173,7 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
            result[i as usize] = status_array[i as usize] == 1;
        }
        unsafe {
            libc::free(status_ptr as *mut libc::c_void);
            nitrokey_sys::NK_free_password_safe_slot_status(status_ptr);
        }
        Ok(result)
    }


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

M src/util.rs => src/util.rs +11 -0
@@ 68,6 68,17 @@ pub fn result_or_error<T>(value: T) -> Result<T, Error> {
    get_last_result().and(Ok(value))
}

pub fn get_struct<R, T, F>(f: F) -> Result<R, Error>
where
    R: From<T>,
    T: Default,
    F: Fn(&mut T) -> c_int,
{
    let mut out = T::default();
    get_command_result(f(&mut out))?;
    Ok(out.into())
}

pub fn get_command_result(value: c_int) -> Result<(), Error> {
    if value == 0 {
        Ok(())