Forgot to CC the list.
-------- Forwarded Message --------
Subject: Re: [PATCH 1/5] Move nitrokey-sys-specific code to backend
Date: Sun, 27 Feb 2022 18:01:53 +0000
From: deso <deso@posteo.net>
To: Robin Krahl <robin.krahl@ireas.org>
Looks good to me. I left a few comments inline.
On 2/24/22 1:54 AM, Robin Krahl wrote:
> With this patch, we move all code that interacts with the nitrokey-sys
> code to a new backend module. This is now the only module that may use
> unsafe code. This is a first step for supporting multiple backends in
> the future.
>
> Note that the structure of the backend will be improved in the future.
> ---
> src/auth.rs | 142 +++--------
> src/backend/conversions.rs | 258 +++++++++++++++++++
> src/backend/mod.rs | 507 +++++++++++++++++++++++++++++++++++++
> src/backend/util.rs | 76 ++++++
> src/config.rs | 71 +-----
> src/device/librem.rs | 12 +-
> src/device/mod.rs | 199 ++-------------
> src/device/pro.rs | 12 +-
> src/device/storage.rs | 187 +++-----------
> src/device/wrapper.rs | 5 +-
> src/error.rs | 9 +-
> src/lib.rs | 96 ++-----
> src/otp.rs | 27 +-
> src/pws.rs | 85 +++----
> src/util.rs | 79 +-----
> 15 files changed, 1041 insertions(+), 724 deletions(-)
> create mode 100644 src/backend/conversions.rs
> create mode 100644 src/backend/mod.rs
> create mode 100644 src/backend/util.rs
>
> diff --git a/src/auth.rs b/src/auth.rs
> index f2b47c1..4dddecf 100644
> --- a/src/auth.rs
> +++ b/src/auth.rs
> @@ -1,25 +1,23 @@
> -// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
> +// Copyright (C) 2018-2021 Robin Krahl <robin.krahl@ireas.org>
> // SPDX-License-Identifier: MIT
>
> -use std::convert::TryInto as _;
> -use std::ffi::CString;
> +use std::ffi::{CStr, CString};
> use std::marker;
> use std::ops;
> -use std::os::raw::c_char;
> -use std::os::raw::c_int;
>
> +use crate::backend::{self, DeviceExt};
> 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};
> +use crate::otp::{ConfigureOtp, GenerateOtp, OtpSlotData};
> +use crate::util::generate_password;
>
> 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<'a> {
> +pub trait Authenticate<'a>: DeviceExt {
> /// 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.
> @@ -65,7 +63,10 @@ pub trait Authenticate<'a> {
> /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
> fn authenticate_user(self, password: &str) -> Result<User<'a, Self>, (Self, Error)>
> where
> - Self: Device<'a> + Sized;
> + Self: Device<'a> + Sized,
> + {
> + authenticate(self, password, backend::Device::authenticate_user)
> + }
>
> /// Performs admin authentication. This method consumes the device. If successful, an
> /// authenticated device is returned. Otherwise, the current unauthenticated device and the
> @@ -112,7 +113,10 @@ pub trait Authenticate<'a> {
> /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
> fn authenticate_admin(self, password: &str) -> Result<Admin<'a, Self>, (Self, Error)>
> where
> - Self: Device<'a> + Sized;
> + Self: Device<'a> + Sized,
> + {
> + authenticate(self, password, backend::Device::authenticate_admin)
> + }
> }
>
> trait AuthenticatedDevice<T> {
> @@ -155,21 +159,19 @@ fn authenticate<'a, D, A, T>(device: D, password: &str, callback: T) -> Result<A
> where
> D: Device<'a>,
> A: AuthenticatedDevice<D>,
> - T: Fn(*const c_char, *const c_char) -> c_int,
> + T: Fn(&backend::Device, &CStr, &CStr) -> Result<(), Error>,
> {
> let temp_password = match generate_password(TEMPORARY_PASSWORD_LENGTH) {
> Ok(temp_password) => temp_password,
> Err(err) => return Err((device, err)),
> };
> - let password = match get_cstring(password) {
> + let password = match CString::new(password) {
> Ok(password) => password,
> - Err(err) => return Err((device, err)),
> + Err(err) => return Err((device, err.into())),
> };
> - let password_ptr = password.as_ptr();
> - let temp_password_ptr = temp_password.as_ptr();
> - match callback(password_ptr, temp_password_ptr) {
> - 0 => Ok(A::new(device, temp_password)),
> - rv => Err((device, Error::from(rv))),
> + match callback(&device.device(), &password, &temp_password) {
> + Ok(()) => Ok(A::new(device, temp_password)),
> + Err(err) => Err((device, err)),
> }
> }
>
> @@ -228,17 +230,15 @@ impl<'a, T: Device<'a>> ops::DerefMut for User<'a, T> {
> }
> }
>
> +impl<'a, T: Device<'a>> DeviceExt for User<'a, 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.as_ptr())
> - })
> + DeviceExt::device(self).get_hotp_code_pin(slot, &self.temp_password)
> }
>
> fn get_totp_code(&self, slot: u8) -> Result<String, Error> {
> - result_from_string(unsafe {
> - nitrokey_sys::NK_get_totp_code_PIN(slot, 0, 0, 0, self.temp_password.as_ptr())
> - })
> + DeviceExt::device(self).get_totp_code_pin(slot, &self.temp_password)
> }
> }
>
> @@ -304,57 +304,35 @@ impl<'a, T: Device<'a>> Admin<'a, T> {
> ///
> /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
> pub fn write_config(&mut self, config: Config) -> Result<(), Error> {
> - get_command_result(unsafe {
> - nitrokey_sys::NK_write_config_struct(config.try_into()?, self.temp_password.as_ptr())
> - })
> + self.device
> + .device()
> + .write_config(config, &self.temp_password)
> }
> }
>
> 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 {
> - nitrokey_sys::NK_write_hotp_slot(
> - raw_data.number,
> - raw_data.name.as_ptr(),
> - raw_data.secret.as_ptr(),
> - counter,
> - raw_data.mode == OtpMode::EightDigits,
> - raw_data.use_enter,
> - raw_data.use_token_id,
> - raw_data.token_id.as_ptr(),
> - self.temp_password.as_ptr(),
> - )
> - })
> + self.device
> + .device()
> + .write_hotp_slot(data, counter, &self.temp_password)
> }
>
> fn write_totp_slot(&mut self, data: OtpSlotData, time_window: u16) -> Result<(), Error> {
> - let raw_data = RawOtpSlotData::new(data)?;
> - get_command_result(unsafe {
> - nitrokey_sys::NK_write_totp_slot(
> - raw_data.number,
> - raw_data.name.as_ptr(),
> - raw_data.secret.as_ptr(),
> - time_window,
> - raw_data.mode == OtpMode::EightDigits,
> - raw_data.use_enter,
> - raw_data.use_token_id,
> - raw_data.token_id.as_ptr(),
> - self.temp_password.as_ptr(),
> - )
> - })
> + self.device
> + .device()
> + .write_totp_slot(data, time_window, &self.temp_password)
> }
>
> fn erase_hotp_slot(&mut self, slot: u8) -> Result<(), Error> {
> - get_command_result(unsafe {
> - nitrokey_sys::NK_erase_hotp_slot(slot, self.temp_password.as_ptr())
> - })
> + self.device
> + .device()
> + .erase_hotp_slot(slot, &self.temp_password)
> }
>
> fn erase_totp_slot(&mut self, slot: u8) -> Result<(), Error> {
> - get_command_result(unsafe {
> - nitrokey_sys::NK_erase_totp_slot(slot, self.temp_password.as_ptr())
> - })
> + self.device
> + .device()
> + .erase_totp_slot(slot, &self.temp_password)
> }
> }
>
> @@ -396,44 +374,8 @@ 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)
> - })
> - }
> +impl<'a> Authenticate<'a> for Librem<'a> {}
>
> - 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> {}
>
> -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<'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 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<'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 Storage<'a> {}
> diff --git a/src/backend/conversions.rs b/src/backend/conversions.rs
> new file mode 100644
> index 0000000..b12fac8
> --- /dev/null
> +++ b/src/backend/conversions.rs
I find the module structure odd. What's the reason for having a module
for all things conversion as opposed to co-locating individual
conversions with the respective types? If we push through with that
style we end up with type definitions scattered all over the place, no?
> @@ -0,0 +1,258 @@
> +// Copyright (C) 2021 Robin Krahl <robin.krahl@ireas.org>
> +// SPDX-License-Identifier: MIT
> +
> +use std::convert::TryFrom;
> +use std::convert::TryInto as _;
> +use std::ffi;
> +
> +use crate::config;
> +use crate::device;
> +use crate::error::{self, Error};
> +
> +use super::util;
> +
> +impl TryFrom<config::Config> for nitrokey_sys::NK_config {
> + type Error = Error;
> +
> + fn try_from(config: 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_config> for config::Config {
> + fn from(config: nitrokey_sys::NK_config) -> Self {
> + config_from_raw(
> + config.numlock,
> + config.capslock,
> + config.scrolllock,
> + config.enable_user_password,
> + )
> + }
> +}
> +
> +impl From<&nitrokey_sys::NK_status> for config::Config {
> + fn from(status: &nitrokey_sys::NK_status) -> Self {
> + config_from_raw(
> + status.config_numlock,
> + status.config_capslock,
> + status.config_scrolllock,
> + status.otp_user_password,
> + )
> + }
> +}
> +
> +fn config_from_raw(
> + numlock: u8,
> + capslock: u8,
> + scrollock: u8,
> + user_password: bool,
> +) -> config::Config {
> + 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,
> + }
> +}
> +
> +fn config_otp_slot_to_option(value: u8) -> Option<u8> {
> + if value < 3 {
Where does the 3 come from? Should it be a named constant?
> + Some(value)
> + } else {
> + None
> + }
> +}
> +
> +fn option_to_config_otp_slot(value: Option<u8>) -> Result<u8, Error> {
> + if let Some(value) = value {
> + if value < 3 {
> + Ok(value)
> + } else {
> + Err(error::LibraryError::InvalidSlot.into())
> + }
Perhaps use config_otp_slot_to_option here?
> + } else {
> + Ok(255)
Similer question to above. Usage of magic number?
> + }
> +}
> +
> +impl TryFrom<&nitrokey_sys::NK_device_info> for device::DeviceInfo {
> + type Error = Error;
> +
> + fn try_from(device_info: &nitrokey_sys::NK_device_info) -> Result<device::DeviceInfo, Error> {
> + let model_result = device_info.model.try_into();
> + let model_option = model_result.map(Some).or_else(|err| match err {
> + Error::UnsupportedModelError => Ok(None),
> + _ => Err(err),
> + })?;
> + let serial_number = unsafe { ffi::CStr::from_ptr(device_info.serial_number) }
> + .to_str()
> + .map_err(Error::from)?;
> + Ok(device::DeviceInfo {
> + model: model_option,
> + path: util::owned_str_from_ptr(device_info.path)?,
> + serial_number: get_hidapi_serial_number(serial_number),
> + })
> + }
> +}
> +
> +/// Parses a serial number returned by hidapi.
> +///
> +/// If the serial number is all zero, this function returns `None`. Otherwise, it uses the last
> +/// eight characters. If these are all zero, the first eight characters are used instead. The
> +/// selected substring is parse as a hex string and its integer value is returned from the
*parsed
> +/// function. If the string cannot be parsed, this function returns `None`.
> +///
> +/// The reason for this behavior is that the Nitrokey Storage does not report its serial number at
> +/// all (all zero value), while the Nitrokey Pro with firmware 0.9 or later writes its serial
> +/// number to the last eight characters. Nitrokey Pro devices with firmware 0.8 or earlier wrote
> +/// their serial number to the first eight characters.
> +fn get_hidapi_serial_number(serial_number: &str) -> Option<device::SerialNumber> {
> + let len = serial_number.len();
> + if len < 8 {
> + // The serial number in the USB descriptor has 12 bytes, we need at least four
Nit: Not sure I understand the comment. If we need at least four and
have twelve we should be good, right? Is there a "more" missing at the
end or something?
> + return None;
> + }
> +
> + let mut iter = serial_number.char_indices().rev();
> + if let Some((i, _)) = iter.find(|(_, c)| *c != '0') {
> + let substr = if len - i < 8 {
> + // The last eight characters contain at least one non-zero character --> use them
> + serial_number.split_at(len - 8).1
> + } else {
> + // The last eight characters are all zero --> use the first eight
> + serial_number.split_at(8).0
> + };
> + substr.parse().ok()
> + } else {
> + // The serial number is all zero
> + None
> + }
> +}
> +
> +impl From<device::Model> for nitrokey_sys::NK_device_model {
> + fn from(model: device::Model) -> Self {
> + match model {
> + device::Model::Librem => nitrokey_sys::NK_device_model_NK_LIBREM,
> + device::Model::Storage => nitrokey_sys::NK_device_model_NK_STORAGE,
> + device::Model::Pro => nitrokey_sys::NK_device_model_NK_PRO,
> + }
> + }
> +}
> +
> +impl TryFrom<nitrokey_sys::NK_device_model> for device::Model {
> + type Error = Error;
> +
> + fn try_from(model: nitrokey_sys::NK_device_model) -> Result<Self, Error> {
> + match model {
> + nitrokey_sys::NK_device_model_NK_DISCONNECTED => {
> + Err(error::CommunicationError::NotConnected.into())
> + }
> + nitrokey_sys::NK_device_model_NK_LIBREM => Ok(device::Model::Librem),
> + nitrokey_sys::NK_device_model_NK_PRO => Ok(device::Model::Pro),
> + nitrokey_sys::NK_device_model_NK_STORAGE => Ok(device::Model::Storage),
> + _ => Err(Error::UnsupportedModelError),
> + }
> + }
> +}
> +
> +impl From<nitrokey_sys::NK_status> for device::Status {
> + fn from(status: nitrokey_sys::NK_status) -> Self {
> + Self {
> + firmware_version: device::FirmwareVersion {
> + major: status.firmware_version_major,
> + minor: status.firmware_version_minor,
> + },
> + serial_number: device::SerialNumber::new(status.serial_number_smart_card),
> + config: config::Config::from(&status),
> + }
> + }
> +}
> +
> +impl From<nitrokey_sys::NK_storage_ProductionTest> for device::StorageProductionInfo {
> + fn from(data: nitrokey_sys::NK_storage_ProductionTest) -> Self {
> + Self {
> + firmware_version: device::FirmwareVersion {
> + major: data.FirmwareVersion_au8[0],
> + minor: data.FirmwareVersion_au8[1],
> + },
> + firmware_version_internal: data.FirmwareVersionInternal_u8,
> + serial_number_cpu: data.CPU_CardID_u32,
> + sd_card: device::SdCardData {
> + serial_number: data.SD_CardID_u32,
> + size: data.SD_Card_Size_u8,
> + manufacturing_year: data.SD_Card_ManufacturingYear_u8,
> + manufacturing_month: data.SD_Card_ManufacturingMonth_u8,
> + oem: data.SD_Card_OEM_u16,
> + manufacturer: data.SD_Card_Manufacturer_u8,
> + },
> + }
> + }
> +}
> +
> +impl From<nitrokey_sys::NK_storage_status> for device::StorageStatus {
> + fn from(status: nitrokey_sys::NK_storage_status) -> Self {
> + Self {
> + unencrypted_volume: device::VolumeStatus {
> + read_only: status.unencrypted_volume_read_only,
> + active: status.unencrypted_volume_active,
> + },
> + encrypted_volume: device::VolumeStatus {
> + read_only: status.encrypted_volume_read_only,
> + active: status.encrypted_volume_active,
> + },
> + hidden_volume: device::VolumeStatus {
> + read_only: status.hidden_volume_read_only,
> + active: status.hidden_volume_active,
> + },
> + firmware_version: device::FirmwareVersion {
> + major: status.firmware_version_major,
> + minor: status.firmware_version_minor,
> + },
> + firmware_locked: status.firmware_locked,
> + serial_number_sd_card: status.serial_number_sd_card,
> + serial_number_smart_card: status.serial_number_smart_card,
> + user_retry_count: status.user_retry_count,
> + admin_retry_count: status.admin_retry_count,
> + new_sd_card_found: status.new_sd_card_found,
> + filled_with_random: status.filled_with_random,
> + stick_initialized: status.stick_initialized,
> + }
> + }
> +}
> +
> +#[cfg(test)]
> +mod tests {
> + use super::get_hidapi_serial_number;
> + use crate::device::SerialNumber;
> +
> + #[test]
> + fn test_get_hidapi_serial_number() {
> + fn assert_none(s: &str) {
> + assert_eq!(None, get_hidapi_serial_number(s));
> + }
> +
> + fn assert_some(n: u32, s: &str) {
> + assert_eq!(Some(SerialNumber::new(n)), get_hidapi_serial_number(s));
> + }
> +
> + assert_none("");
> + assert_none("00000000000000000");
> + assert_none("blubb");
> + assert_none("1234");
> +
> + assert_some(0x1234, "00001234");
> + assert_some(0x1234, "000000001234");
> + assert_some(0x1234, "100000001234");
> + assert_some(0x12340000, "123400000000");
> + assert_some(0x5678, "000000000000000000005678");
> + assert_some(0x1234, "000012340000000000000000");
> + assert_some(0xffff, "00000000000000000000FFFF");
> + assert_some(0xffff, "00000000000000000000ffff");
> + }
> +}
> diff --git a/src/backend/mod.rs b/src/backend/mod.rs
> new file mode 100644
> index 0000000..6a2c4e2
> --- /dev/null
> +++ b/src/backend/mod.rs
> @@ -0,0 +1,507 @@
> +// Copyright (C) 2021-2022 Robin Krahl <robin.krahl@ireas.org>
> +// SPDX-License-Identifier: MIT
> +
> +// We need unsafe code to interact with nitrokey-sys.
> +#![allow(unsafe_code)]
> +
> +mod conversions;
> +mod util;
> +
> +use std::convert::TryFrom as _;
> +use std::convert::TryInto as _;
> +use std::ffi;
> +use std::ops;
> +use std::ptr;
> +
> +use crate::config;
> +use crate::device;
> +use crate::error::{self, Error};
> +use crate::otp;
> +use crate::Version;
> +
> +pub fn set_debug(state: bool) {
> + unsafe {
> + nitrokey_sys::NK_set_debug(state);
> + }
> +}
> +
> +pub fn set_log_level(level: crate::util::LogLevel) {
> + unsafe {
> + nitrokey_sys::NK_set_debug_level(level.into());
> + }
> +}
> +
> +pub fn get_library_version() -> Result<Version, Error> {
> + // NK_get_library_version returns a static string, so we don’t have to free the pointer.
> + let git = unsafe { nitrokey_sys::NK_get_library_version() };
> + let git = if git.is_null() {
> + String::new()
> + } else {
> + util::owned_str_from_ptr(git)?
> + };
> + let major = unsafe { nitrokey_sys::NK_get_major_library_version() };
> + let minor = unsafe { nitrokey_sys::NK_get_minor_library_version() };
> + Ok(Version { git, major, minor })
> +}
> +
> +pub fn list_devices() -> Result<Vec<device::DeviceInfo>, Error> {
> + let ptr = ptr::NonNull::new(unsafe { nitrokey_sys::NK_list_devices() });
> + match ptr {
> + Some(mut ptr) => {
> + let mut vec: Vec<device::DeviceInfo> = Vec::new();
> + push_device_info(&mut vec, unsafe { ptr.as_ref() })?;
> + unsafe {
> + nitrokey_sys::NK_free_device_info(ptr.as_mut());
> + }
> + Ok(vec)
> + }
> + None => util::get_last_result().map(|_| Vec::new()),
> + }
> +}
> +
> +fn push_device_info(
> + vec: &mut Vec<device::DeviceInfo>,
> + info: &nitrokey_sys::NK_device_info,
> +) -> Result<(), Error> {
> + vec.push(info.try_into()?);
> + if let Some(ptr) = ptr::NonNull::new(info.next) {
> + push_device_info(vec, unsafe { ptr.as_ref() })?;
Nit: While somewhat elegant, perhaps just use an iterative version to
eliminate the risk of stack overflows if the compiler doesn't tail optimize?
> + }
> + Ok(())
> +}
> +
> +pub fn connect() -> Result<device::Model, Error> {
> + if unsafe { nitrokey_sys::NK_login_auto() } == 1 {
> + device::Model::try_from(unsafe { nitrokey_sys::NK_get_device_model() })
> + } else {
> + Err(error::CommunicationError::NotConnected.into())
> + }
> +}
> +
> +pub fn connect_model(model: device::Model) -> Result<(), Error> {
> + if unsafe { nitrokey_sys::NK_login_enum(model.into()) == 1 } {
> + Ok(())
> + } else {
> + Err(error::CommunicationError::NotConnected.into())
> + }
> +}
> +
> +pub fn connect_path(path: &ffi::CString) -> Result<device::Model, Error> {
> + if unsafe { nitrokey_sys::NK_connect_with_path(path.as_ptr()) } == 1 {
> + device::Model::try_from(unsafe { nitrokey_sys::NK_get_device_model() })
> + } else {
> + Err(error::CommunicationError::NotConnected.into())
> + }
> +}
> +
> +#[derive(Debug)]
> +pub struct Device {
> + _private: (),
> +}
> +
> +impl Device {
> + fn new() -> Self {
> + Self { _private: () }
> + }
> +
> + pub fn get_serial_number(&self) -> Result<device::SerialNumber, Error> {
> + let serial_number = unsafe { nitrokey_sys::NK_device_serial_number_as_u32() };
> + util::result_or_error(device::SerialNumber::new(serial_number))
> + }
> +
> + pub fn get_user_retry_count(&self) -> Result<u8, Error> {
> + util::result_or_error(unsafe { nitrokey_sys::NK_get_user_retry_count() })
> + }
> +
> + pub fn get_admin_retry_count(&self) -> Result<u8, Error> {
> + util::result_or_error(unsafe { nitrokey_sys::NK_get_admin_retry_count() })
> + }
> +
> + pub fn get_firmware_version(&self) -> Result<device::FirmwareVersion, Error> {
> + unsafe {
> + let major = util::result_or_error(nitrokey_sys::NK_get_major_firmware_version())?;
> + let minor = util::result_or_error(nitrokey_sys::NK_get_minor_firmware_version())?;
> + Ok(device::FirmwareVersion { major, minor })
> + }
> + }
> +
> + pub fn get_config(&self) -> Result<config::Config, Error> {
> + util::get_struct(|out| unsafe { nitrokey_sys::NK_read_config_struct(out) })
> + }
> +
> + pub fn change_admin_pin(&self, current: &ffi::CStr, new: &ffi::CStr) -> Result<(), Error> {
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_change_admin_PIN(current.as_ptr(), new.as_ptr())
> + })
> + }
> +
> + pub fn change_user_pin(&self, current: &ffi::CStr, new: &ffi::CStr) -> Result<(), Error> {
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_change_user_PIN(current.as_ptr(), new.as_ptr())
> + })
> + }
> +
> + pub fn unlock_user_pin(
> + &self,
> + admin_pin: &ffi::CStr,
> + user_pin: &ffi::CStr,
> + ) -> Result<(), Error> {
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_unlock_user_password(admin_pin.as_ptr(), user_pin.as_ptr())
> + })
> + }
> +
> + pub fn lock(&self) -> Result<(), Error> {
> + util::get_command_result(unsafe { nitrokey_sys::NK_lock_device() })
> + }
> +
> + pub fn factory_reset(&self, admin_pin: &ffi::CStr) -> Result<(), Error> {
> + util::get_command_result(unsafe { nitrokey_sys::NK_factory_reset(admin_pin.as_ptr()) })
> + }
> +
> + pub fn build_aes_key(&self, admin_pin: &ffi::CStr) -> Result<(), Error> {
> + util::get_command_result(unsafe { nitrokey_sys::NK_build_aes_key(admin_pin.as_ptr()) })
> + }
> +
> + pub fn logout(&self) {
> + unsafe {
> + nitrokey_sys::NK_logout();
> + }
> + }
> +
> + pub fn get_status(&self) -> Result<device::Status, Error> {
> + util::get_struct(|out| unsafe { nitrokey_sys::NK_get_status(out) })
> + }
> +
> + pub fn change_update_pin(&self, current: &ffi::CStr, new: &ffi::CStr) -> Result<(), Error> {
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_change_update_password(current.as_ptr(), new.as_ptr())
> + })
> + }
> +
> + pub fn enable_firmware_update(&self, update_pin: &ffi::CStr) -> Result<(), Error> {
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_enable_firmware_update(update_pin.as_ptr())
> + })
> + }
> +
> + pub fn enable_encrypted_volume(&self, user_pin: &ffi::CStr) -> Result<(), Error> {
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_unlock_encrypted_volume(user_pin.as_ptr())
> + })
> + }
> +
> + pub fn disable_encrypted_volume(&self) -> Result<(), Error> {
> + util::get_command_result(unsafe { nitrokey_sys::NK_lock_encrypted_volume() })
> + }
> +
> + pub fn enable_hidden_volume(&self, volume_password: &ffi::CStr) -> Result<(), Error> {
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_unlock_hidden_volume(volume_password.as_ptr())
> + })
> + }
> +
> + pub fn disable_hidden_volume(&self) -> Result<(), Error> {
> + util::get_command_result(unsafe { nitrokey_sys::NK_lock_hidden_volume() })
> + }
> +
> + pub fn create_hidden_volume(
> + &self,
> + slot: u8,
> + start: u8,
> + end: u8,
> + password: &ffi::CStr,
> + ) -> Result<(), Error> {
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_create_hidden_volume(slot, start, end, password.as_ptr())
> + })
> + }
> +
> + pub fn set_unencrypted_volume_mode(
> + &self,
> + admin_pin: &ffi::CStr,
> + mode: device::VolumeMode,
> + ) -> Result<(), Error> {
> + let result = match mode {
> + device::VolumeMode::ReadOnly => unsafe {
> + nitrokey_sys::NK_set_unencrypted_read_only_admin(admin_pin.as_ptr())
> + },
> + device::VolumeMode::ReadWrite => unsafe {
> + nitrokey_sys::NK_set_unencrypted_read_write_admin(admin_pin.as_ptr())
> + },
> + };
> + util::get_command_result(result)
> + }
> +
> + pub fn set_encrypted_volume_mode(
> + &self,
> + admin_pin: &ffi::CStr,
> + mode: device::VolumeMode,
> + ) -> Result<(), Error> {
> + let result = match mode {
> + device::VolumeMode::ReadOnly => unsafe {
> + nitrokey_sys::NK_set_encrypted_read_only(admin_pin.as_ptr())
> + },
> + device::VolumeMode::ReadWrite => unsafe {
> + nitrokey_sys::NK_set_encrypted_read_write(admin_pin.as_ptr())
> + },
> + };
> + util::get_command_result(result)
> + }
> +
> + pub fn get_storage_status(&self) -> Result<device::StorageStatus, Error> {
> + util::get_struct(|out| unsafe { nitrokey_sys::NK_get_status_storage(out) })
> + }
> +
> + pub fn get_production_info(&self) -> Result<device::StorageProductionInfo, Error> {
> + util::get_struct(|out| unsafe { nitrokey_sys::NK_get_storage_production_info(out) })
> + }
> +
> + pub fn clear_new_sd_card_warning(&self, admin_pin: &ffi::CString) -> Result<(), Error> {
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_clear_new_sd_card_warning(admin_pin.as_ptr())
> + })
> + }
> +
> + pub fn get_sd_card_usage(&self) -> Result<ops::Range<u8>, Error> {
> + 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 util::get_command_result(result) {
> + Ok(_) => {
> + if usage_data.write_level_min > usage_data.write_level_max
> + || usage_data.write_level_max > 100
> + {
> + Err(Error::UnexpectedError("Invalid write levels".to_owned()))
> + } else {
> + Ok(ops::Range {
> + start: usage_data.write_level_min,
> + end: usage_data.write_level_max,
> + })
> + }
> + }
> + Err(err) => Err(err),
> + }
> + }
> +
> + pub fn wink(&self) -> Result<(), Error> {
> + util::get_command_result(unsafe { nitrokey_sys::NK_wink() })
> + }
> +
> + pub fn get_operation_status(&self) -> Result<device::OperationStatus, Error> {
> + let status = unsafe { nitrokey_sys::NK_get_progress_bar_value() };
> + match status {
> + 0..=100 => u8::try_from(status)
> + .map(device::OperationStatus::Ongoing)
> + .map_err(|_| {
> + Error::UnexpectedError("Cannot create u8 from operation status".to_owned())
> + }),
Nit: Should be safe to just unwrap here, given that we know the input
range and that nothing can go wrong?
> + -1 => Ok(device::OperationStatus::Idle),
> + -2 => Err(util::get_last_error()),
> + _ => Err(Error::UnexpectedError(
> + "Invalid operation status".to_owned(),
> + )),
> + }
> + }
> +
> + pub fn fill_sd_card(&self, admin_pin: &ffi::CStr) -> Result<(), Error> {
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_fill_SD_card_with_random_data(admin_pin.as_ptr())
> + })
> + .or_else(|err| match err {
> + // libnitrokey’s C API returns a LongOperationInProgressException with the same error
> + // code as the WrongCrc command error, so we cannot distinguish them.
> + Error::CommandError(error::CommandError::WrongCrc) => Ok(()),
Is this something that upstream knows about and is on the radar for
being fixed?
> + err => Err(err),
> + })
> + }
> +
> + pub fn export_firmware(&self, admin_pin: &ffi::CStr) -> Result<(), Error> {
> + util::get_command_result(unsafe { nitrokey_sys::NK_export_firmware(admin_pin.as_ptr()) })
> + }
> +
> + pub fn set_time(&self, time: u64, force: bool) -> Result<(), Error> {
> + let result = if force {
> + unsafe { nitrokey_sys::NK_totp_set_time(time) }
> + } else {
> + unsafe { nitrokey_sys::NK_totp_set_time_soft(time) }
> + };
> + util::get_command_result(result)
> + }
> +
> + pub fn get_hotp_slot_name(&self, slot: u8) -> Result<String, Error> {
> + util::result_from_string(unsafe { nitrokey_sys::NK_get_hotp_slot_name(slot) })
> + }
> +
> + pub fn get_totp_slot_name(&self, slot: u8) -> Result<String, Error> {
> + util::result_from_string(unsafe { nitrokey_sys::NK_get_totp_slot_name(slot) })
> + }
> +
> + pub fn get_hotp_code(&self, slot: u8) -> Result<String, Error> {
> + util::result_from_string(unsafe { nitrokey_sys::NK_get_hotp_code(slot) })
> + }
> +
> + pub fn get_hotp_code_pin(&self, slot: u8, temp_password: &ffi::CStr) -> Result<String, Error> {
> + util::result_from_string(unsafe {
> + nitrokey_sys::NK_get_hotp_code_PIN(slot, temp_password.as_ptr())
> + })
> + }
> +
> + pub fn get_totp_code(&self, slot: u8) -> Result<String, Error> {
> + util::result_from_string(unsafe { nitrokey_sys::NK_get_totp_code(slot, 0, 0, 0) })
> + }
> +
> + pub fn get_totp_code_pin(&self, slot: u8, temp_password: &ffi::CStr) -> Result<String, Error> {
> + util::result_from_string(unsafe {
> + nitrokey_sys::NK_get_totp_code_PIN(slot, 0, 0, 0, temp_password.as_ptr())
> + })
> + }
> +
> + pub fn write_config(
> + &self,
> + config: config::Config,
> + temp_password: &ffi::CStr,
> + ) -> Result<(), Error> {
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_write_config_struct(config.try_into()?, temp_password.as_ptr())
> + })
> + }
> +
> + pub fn write_hotp_slot(
> + &self,
> + data: otp::OtpSlotData,
> + counter: u64,
> + temp_password: &ffi::CStr,
> + ) -> Result<(), Error> {
> + let raw_data = otp::RawOtpSlotData::new(data)?;
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_write_hotp_slot(
> + raw_data.number,
> + raw_data.name.as_ptr(),
> + raw_data.secret.as_ptr(),
> + counter,
> + raw_data.mode == otp::OtpMode::EightDigits,
> + raw_data.use_enter,
> + raw_data.use_token_id,
> + raw_data.token_id.as_ptr(),
> + temp_password.as_ptr(),
> + )
> + })
> + }
> +
> + pub fn write_totp_slot(
> + &self,
> + data: otp::OtpSlotData,
> + time_window: u16,
> + temp_password: &ffi::CStr,
> + ) -> Result<(), Error> {
> + let raw_data = otp::RawOtpSlotData::new(data)?;
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_write_totp_slot(
> + raw_data.number,
> + raw_data.name.as_ptr(),
> + raw_data.secret.as_ptr(),
> + time_window,
> + raw_data.mode == otp::OtpMode::EightDigits,
> + raw_data.use_enter,
> + raw_data.use_token_id,
> + raw_data.token_id.as_ptr(),
> + temp_password.as_ptr(),
> + )
> + })
> + }
> +
> + pub fn erase_hotp_slot(&self, slot: u8, temp_password: &ffi::CStr) -> Result<(), Error> {
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_erase_hotp_slot(slot, temp_password.as_ptr())
> + })
> + }
> +
> + pub fn erase_totp_slot(&self, slot: u8, temp_password: &ffi::CStr) -> Result<(), Error> {
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_erase_totp_slot(slot, temp_password.as_ptr())
> + })
> + }
> +
> + pub fn authenticate_user(
> + &self,
> + password: &ffi::CStr,
> + temp_password: &ffi::CStr,
> + ) -> Result<(), Error> {
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_user_authenticate(password.as_ptr(), temp_password.as_ptr())
> + })
> + }
> +
> + pub fn authenticate_admin(
> + &self,
> + password: &ffi::CStr,
> + temp_password: &ffi::CStr,
> + ) -> Result<(), Error> {
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_first_authenticate(password.as_ptr(), temp_password.as_ptr())
> + })
> + }
> +
> + pub fn enable_password_safe(&self, user_pin: &ffi::CStr) -> Result<(), Error> {
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_enable_password_safe(user_pin.as_ptr())
> + })
> + }
> +
> + #[allow(deprecated)]
> + pub fn get_pws_slot_status(&self) -> Result<[bool; 16], Error> {
> + let status_ptr = unsafe { nitrokey_sys::NK_get_password_safe_slot_status() };
> + if status_ptr.is_null() {
> + return Err(util::get_last_error());
> + }
> + let status_array_ptr = status_ptr as *const [u8; crate::SLOT_COUNT as usize];
> + let status_array = unsafe { *status_array_ptr };
> + let mut result = [false; crate::SLOT_COUNT as usize];
> + for i in 0..crate::SLOT_COUNT {
> + result[i as usize] = status_array[i as usize] == 1;
> + }
> + unsafe {
> + nitrokey_sys::NK_free_password_safe_slot_status(status_ptr);
> + }
> + Ok(result)
> + }
> +
> + pub fn get_pws_slot_name(&self, slot: u8) -> Result<String, Error> {
> + util::result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_name(slot) })
> + }
> +
> + pub fn get_pws_slot_login(&self, slot: u8) -> Result<String, Error> {
> + util::result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_login(slot) })
> + }
> +
> + pub fn get_pws_slot_password(&self, slot: u8) -> Result<String, Error> {
> + util::result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_password(slot) })
> + }
> +
> + pub fn write_pws_slot(
> + &self,
> + slot: u8,
> + name: &ffi::CStr,
> + login: &ffi::CStr,
> + password: &ffi::CStr,
> + ) -> Result<(), Error> {
> + util::get_command_result(unsafe {
> + nitrokey_sys::NK_write_password_safe_slot(
> + slot,
> + name.as_ptr(),
> + login.as_ptr(),
> + password.as_ptr(),
> + )
> + })
> + }
> +
> + pub fn erase_pws_slot(&mut self, slot: u8) -> Result<(), Error> {
> + util::get_command_result(unsafe { nitrokey_sys::NK_erase_password_safe_slot(slot) })
> + }
> +}
> +
> +pub trait DeviceExt {
> + fn device(&self) -> Device {
> + Device::new()
> + }
> +}
> diff --git a/src/backend/util.rs b/src/backend/util.rs
> new file mode 100644
> index 0000000..6835b55
> --- /dev/null
> +++ b/src/backend/util.rs
> @@ -0,0 +1,76 @@
> +// Copyright (C) 2018-2021 Robin Krahl <robin.krahl@ireas.org>
> +// SPDX-License-Identifier: MIT
> +
> +use std::ffi::CStr;
> +use std::os::raw::{c_char, c_int};
> +
> +use libc::{c_void, free};
> +
> +use crate::error::Error;
> +
> +fn str_from_ptr<'a>(ptr: *const c_char) -> Result<&'a str, Error> {
> + unsafe { CStr::from_ptr(ptr) }.to_str().map_err(Error::from)
> +}
> +
> +pub fn owned_str_from_ptr(ptr: *const c_char) -> Result<String, Error> {
I guess it's just a move, but it seems the function cannot handle ptr
being NULL, right? Anything accepting a raw pointer should probably be
unsafe, because a garbage pointer can always cause a safety violation.
Here specifically, perhaps we could also make it accept a NonNull instead?
> + str_from_ptr(ptr).map(ToOwned::to_owned)
> +}
> +
> +fn run_with_string<R, F>(ptr: *const c_char, op: F) -> Result<R, Error>
> +where
> + F: FnOnce(&str) -> Result<R, Error>,
> +{
> + if ptr.is_null() {
> + return Err(Error::UnexpectedError(
> + "libnitrokey returned a null pointer".to_owned(),
> + ));
> + }
> + let result = str_from_ptr(ptr).and_then(op);
> + unsafe { free(ptr as *mut c_void) };
> + result
> +}
> +
> +pub fn result_from_string(ptr: *const c_char) -> Result<String, Error> {
> + run_with_string(ptr, |s| {
> + // An empty string can both indicate an error or be a valid return value. In this case, we
> + // have to check the last command status to decide what to return.
> + if s.is_empty() {
> + get_last_result().map(|_| s.to_owned())
> + } else {
> + Ok(s.to_owned())
> + }
> + })
> +}
> +
> +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(())
> + } else {
> + Err(Error::from(value))
> + }
> +}
> +
> +pub fn get_last_result() -> Result<(), Error> {
> + get_command_result(unsafe { nitrokey_sys::NK_get_last_command_status() }.into())
> +}
> +
> +pub fn get_last_error() -> Error {
> + get_last_result().err().unwrap_or_else(|| {
> + Error::UnexpectedError("Expected an error, but command status is zero".to_owned())
> + })
> +}
> diff --git a/src/config.rs b/src/config.rs
> index be31fee..1e86196 100644
> --- a/src/config.rs
> +++ b/src/config.rs
> @@ -1,10 +1,6 @@
> -// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
> +// Copyright (C) 2018-2021 Robin Krahl <robin.krahl@ireas.org>
> // SPDX-License-Identifier: MIT
>
> -use std::convert;
> -
> -use crate::error::{Error, LibraryError};
> -
> /// The configuration for a Nitrokey.
> #[derive(Clone, Copy, Debug, PartialEq)]
> pub struct Config {
> @@ -25,26 +21,6 @@ pub struct Config {
> pub user_password: bool,
> }
>
> -fn config_otp_slot_to_option(value: u8) -> Option<u8> {
> - if value < 3 {
> - Some(value)
> - } else {
> - None
> - }
> -}
> -
> -fn option_to_config_otp_slot(value: Option<u8>) -> Result<u8, Error> {
> - if let Some(value) = value {
> - if value < 3 {
> - Ok(value)
> - } else {
> - Err(LibraryError::InvalidSlot.into())
> - }
> - } else {
> - Ok(255)
> - }
> -}
> -
> impl Config {
> /// Constructs a new instance of this struct.
> pub fn new(
> @@ -60,49 +36,4 @@ 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 nitrokey_sys::NK_config {
> - type Error = Error;
> -
> - 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_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 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,
> - )
> - }
> }
> diff --git a/src/device/librem.rs b/src/device/librem.rs
> index ea482c1..371f366 100644
> --- a/src/device/librem.rs
> +++ b/src/device/librem.rs
> @@ -1,10 +1,10 @@
> -// Copyright (C) 2018-2020 Robin Krahl <robin.krahl@ireas.org>
> +// Copyright (C) 2018-2021 Robin Krahl <robin.krahl@ireas.org>
> // SPDX-License-Identifier: MIT
>
> +use crate::backend::DeviceExt;
> 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.
> ///
> @@ -60,9 +60,7 @@ impl<'a> Librem<'a> {
>
> impl<'a> Drop for Librem<'a> {
> fn drop(&mut self) {
> - unsafe {
> - nitrokey_sys::NK_logout();
> - }
> + self.device().logout()
> }
> }
>
> @@ -76,8 +74,10 @@ impl<'a> Device<'a> for Librem<'a> {
> }
>
> fn get_status(&self) -> Result<Status, Error> {
> - get_struct(|out| unsafe { nitrokey_sys::NK_get_status(out) })
> + self.device().get_status()
> }
> }
>
> +impl<'a> DeviceExt for Librem<'a> {}
> +
> impl<'a> GenerateOtp for Librem<'a> {}
> diff --git a/src/device/mod.rs b/src/device/mod.rs
> index 5f55927..eea8d29 100644
> --- a/src/device/mod.rs
> +++ b/src/device/mod.rs
> @@ -1,4 +1,4 @@
> -// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
> +// Copyright (C) 2018-2021 Robin Krahl <robin.krahl@ireas.org>
> // SPDX-License-Identifier: MIT
>
> mod librem;
> @@ -6,19 +6,16 @@ mod pro;
> mod storage;
> mod wrapper;
>
> -use std::convert::{TryFrom, TryInto};
> use std::ffi;
> use std::fmt;
> use std::str;
>
> use crate::auth::Authenticate;
> +use crate::backend;
> use crate::config::Config;
> -use crate::error::{CommunicationError, Error, LibraryError};
> +use crate::error::{Error, LibraryError};
> use crate::otp::GenerateOtp;
> use crate::pws::GetPasswordSafe;
> -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;
> @@ -50,32 +47,6 @@ 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,
> - }
> - }
> -}
> -
> -impl TryFrom<nitrokey_sys::NK_device_model> for Model {
> - type Error = Error;
> -
> - fn try_from(model: nitrokey_sys::NK_device_model) -> Result<Self, Error> {
> - match 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),
> - }
> - }
> -}
> -
> /// Serial number of a Nitrokey device.
> ///
> /// The serial number can be formatted as a string using the [`ToString`][] trait, and it can be
> @@ -103,7 +74,7 @@ impl SerialNumber {
> SerialNumber::new(0)
> }
>
> - fn new(value: u32) -> Self {
> + pub(crate) fn new(value: u32) -> Self {
> SerialNumber { value }
> }
>
> @@ -179,26 +150,6 @@ pub struct DeviceInfo {
> pub serial_number: Option<SerialNumber>,
> }
>
> -impl TryFrom<&nitrokey_sys::NK_device_info> for DeviceInfo {
> - type Error = Error;
> -
> - fn try_from(device_info: &nitrokey_sys::NK_device_info) -> Result<DeviceInfo, Error> {
> - let model_result = device_info.model.try_into();
> - let model_option = model_result.map(Some).or_else(|err| match err {
> - Error::UnsupportedModelError => Ok(None),
> - _ => Err(err),
> - })?;
> - let serial_number = unsafe { ffi::CStr::from_ptr(device_info.serial_number) }
> - .to_str()
> - .map_err(Error::from)?;
> - Ok(DeviceInfo {
> - model: model_option,
> - path: owned_str_from_ptr(device_info.path)?,
> - serial_number: get_hidapi_serial_number(serial_number),
> - })
> - }
> -}
> -
> impl fmt::Display for DeviceInfo {
> fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
> match self.model {
> @@ -213,40 +164,6 @@ impl fmt::Display for DeviceInfo {
> }
> }
>
> -/// Parses a serial number returned by hidapi.
> -///
> -/// If the serial number is all zero, this function returns `None`. Otherwise, it uses the last
> -/// eight characters. If these are all zero, the first eight characters are used instead. The
> -/// selected substring is parse as a hex string and its integer value is returned from the
> -/// function. If the string cannot be parsed, this function returns `None`.
> -///
> -/// The reason for this behavior is that the Nitrokey Storage does not report its serial number at
> -/// all (all zero value), while the Nitrokey Pro with firmware 0.9 or later writes its serial
> -/// number to the last eight characters. Nitrokey Pro devices with firmware 0.8 or earlier wrote
> -/// their serial number to the first eight characters.
> -fn get_hidapi_serial_number(serial_number: &str) -> Option<SerialNumber> {
> - let len = serial_number.len();
> - if len < 8 {
> - // The serial number in the USB descriptor has 12 bytes, we need at least four
> - return None;
> - }
> -
> - let mut iter = serial_number.char_indices().rev();
> - if let Some((i, _)) = iter.find(|(_, c)| *c != '0') {
> - let substr = if len - i < 8 {
> - // The last eight characters contain at least one non-zero character --> use them
> - serial_number.split_at(len - 8).1
> - } else {
> - // The last eight characters are all zero --> use the first eight
> - serial_number.split_at(8).0
> - };
> - substr.parse().ok()
> - } else {
> - // The serial number is all zero
> - None
> - }
> -}
> -
> /// A firmware version for a Nitrokey device.
> #[derive(Clone, Copy, Debug, PartialEq)]
> pub struct FirmwareVersion {
> @@ -273,24 +190,13 @@ pub struct Status {
> pub config: Config,
> }
>
> -impl From<nitrokey_sys::NK_status> for Status {
> - fn from(status: nitrokey_sys::NK_status) -> Self {
> - Self {
> - firmware_version: FirmwareVersion {
> - major: status.firmware_version_major,
> - minor: status.firmware_version_minor,
> - },
> - serial_number: SerialNumber::new(status.serial_number_smart_card),
> - config: Config::from(&status),
> - }
> - }
> -}
> -
> /// A Nitrokey device.
> ///
> /// This trait provides the commands that can be executed without authentication and that are
> /// present on all supported Nitrokey devices.
> -pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt::Debug {
> +pub trait Device<'a>:
> + Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + backend::DeviceExt + fmt::Debug
> +{
> /// Returns the [`Manager`][] instance that has been used to connect to this device.
> ///
> /// # Example
> @@ -382,8 +288,7 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
> /// # }
> /// ```
> fn get_serial_number(&self) -> Result<SerialNumber, Error> {
> - let serial_number = unsafe { nitrokey_sys::NK_device_serial_number_as_u32() };
> - result_or_error(SerialNumber::new(serial_number))
> + self.device().get_serial_number()
> }
>
> /// Returns the number of remaining authentication attempts for the user. The total number of
> @@ -407,7 +312,7 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
> /// # }
> /// ```
> fn get_user_retry_count(&self) -> Result<u8, Error> {
> - result_or_error(unsafe { nitrokey_sys::NK_get_user_retry_count() })
> + self.device().get_user_retry_count()
> }
>
> /// Returns the number of remaining authentication attempts for the admin. The total number of
> @@ -431,7 +336,7 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
> /// # }
> /// ```
> fn get_admin_retry_count(&self) -> Result<u8, Error> {
> - result_or_error(unsafe { nitrokey_sys::NK_get_admin_retry_count() })
> + self.device().get_admin_retry_count()
> }
>
> /// Returns the firmware version.
> @@ -453,9 +358,7 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
> /// # }
> /// ```
> fn get_firmware_version(&self) -> Result<FirmwareVersion, Error> {
> - let major = result_or_error(unsafe { nitrokey_sys::NK_get_major_firmware_version() })?;
> - let minor = result_or_error(unsafe { nitrokey_sys::NK_get_minor_firmware_version() })?;
> - Ok(FirmwareVersion { major, minor })
> + self.device().get_firmware_version()
> }
>
> /// Returns the current configuration of the Nitrokey device.
> @@ -478,7 +381,7 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
> /// # }
> /// ```
> fn get_config(&self) -> Result<Config, Error> {
> - get_struct(|out| unsafe { nitrokey_sys::NK_read_config_struct(out) })
> + self.device().get_config()
> }
>
> /// Changes the administrator PIN.
> @@ -508,11 +411,9 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
> /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
> /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
> fn change_admin_pin(&mut self, current: &str, new: &str) -> Result<(), Error> {
> - let current_string = get_cstring(current)?;
> - let new_string = get_cstring(new)?;
> - get_command_result(unsafe {
> - nitrokey_sys::NK_change_admin_PIN(current_string.as_ptr(), new_string.as_ptr())
> - })
> + let current = ffi::CString::new(current)?;
> + let new = ffi::CString::new(new)?;
> + self.device().change_admin_pin(¤t, &new)
> }
>
> /// Changes the user PIN.
> @@ -542,11 +443,9 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
> /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
> /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
> fn change_user_pin(&mut self, current: &str, new: &str) -> Result<(), Error> {
> - let current_string = get_cstring(current)?;
> - let new_string = get_cstring(new)?;
> - get_command_result(unsafe {
> - nitrokey_sys::NK_change_user_PIN(current_string.as_ptr(), new_string.as_ptr())
> - })
> + let current = ffi::CString::new(current)?;
> + let new = ffi::CString::new(new)?;
> + self.device().change_user_pin(¤t, &new)
> }
>
> /// Unlocks the user PIN after three failed login attempts and sets it to the given value.
> @@ -576,14 +475,9 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
> /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
> /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
> fn unlock_user_pin(&mut self, admin_pin: &str, user_pin: &str) -> Result<(), Error> {
> - let admin_pin_string = get_cstring(admin_pin)?;
> - let user_pin_string = get_cstring(user_pin)?;
> - get_command_result(unsafe {
> - nitrokey_sys::NK_unlock_user_password(
> - admin_pin_string.as_ptr(),
> - user_pin_string.as_ptr(),
> - )
> - })
> + let admin_pin = ffi::CString::new(admin_pin)?;
> + let user_pin = ffi::CString::new(user_pin)?;
> + self.device().unlock_user_pin(&admin_pin, &user_pin)
> }
>
> /// Locks the Nitrokey device.
> @@ -608,7 +502,7 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
> /// # }
> /// ```
> fn lock(&mut self) -> Result<(), Error> {
> - get_command_result(unsafe { nitrokey_sys::NK_lock_device() })
> + self.device().lock()
> }
>
> /// Performs a factory reset on the Nitrokey device.
> @@ -644,8 +538,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
> /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
> /// [`build_aes_key`]: #method.build_aes_key
> fn factory_reset(&mut self, admin_pin: &str) -> Result<(), Error> {
> - let admin_pin_string = get_cstring(admin_pin)?;
> - get_command_result(unsafe { nitrokey_sys::NK_factory_reset(admin_pin_string.as_ptr()) })
> + let admin_pin = ffi::CString::new(admin_pin)?;
> + self.device().factory_reset(&admin_pin)
> }
>
> /// Builds a new AES key on the Nitrokey.
> @@ -681,15 +575,11 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
> /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
> /// [`factory_reset`]: #method.factory_reset
> fn build_aes_key(&mut self, admin_pin: &str) -> Result<(), Error> {
> - let admin_pin_string = get_cstring(admin_pin)?;
> - get_command_result(unsafe { nitrokey_sys::NK_build_aes_key(admin_pin_string.as_ptr()) })
> + let admin_pin = ffi::CString::new(admin_pin)?;
> + self.device().build_aes_key(&admin_pin)
> }
> }
>
> -fn get_connected_model() -> Result<Model, Error> {
> - Model::try_from(unsafe { nitrokey_sys::NK_get_device_model() })
> -}
> -
> pub(crate) fn create_device_wrapper(
> manager: &mut crate::Manager,
> model: Model,
> @@ -701,21 +591,11 @@ pub(crate) fn create_device_wrapper(
> }
> }
>
> -pub(crate) fn get_connected_device(
> - manager: &mut crate::Manager,
> -) -> Result<DeviceWrapper<'_>, Error> {
> - Ok(create_device_wrapper(manager, get_connected_model()?))
> -}
> -
> -pub(crate) fn connect_enum(model: Model) -> bool {
> - unsafe { nitrokey_sys::NK_login_enum(model.into()) == 1 }
> -}
> -
> #[cfg(test)]
> mod tests {
> use std::str::FromStr;
>
> - use super::{get_hidapi_serial_number, LibraryError, SerialNumber};
> + use super::{LibraryError, SerialNumber};
>
> #[test]
> fn test_serial_number_display() {
> @@ -757,29 +637,4 @@ mod tests {
> assert_err("blubb");
> assert_err("");
> }
> -
> - #[test]
> - fn test_get_hidapi_serial_number() {
> - fn assert_none(s: &str) {
> - assert_eq!(None, get_hidapi_serial_number(s));
> - }
> -
> - fn assert_some(n: u32, s: &str) {
> - assert_eq!(Some(SerialNumber::new(n)), get_hidapi_serial_number(s));
> - }
> -
> - assert_none("");
> - assert_none("00000000000000000");
> - assert_none("blubb");
> - assert_none("1234");
> -
> - assert_some(0x1234, "00001234");
> - assert_some(0x1234, "000000001234");
> - assert_some(0x1234, "100000001234");
> - assert_some(0x12340000, "123400000000");
> - assert_some(0x5678, "000000000000000000005678");
> - assert_some(0x1234, "000012340000000000000000");
> - assert_some(0xffff, "00000000000000000000FFFF");
> - assert_some(0xffff, "00000000000000000000ffff");
> - }
> }
> diff --git a/src/device/pro.rs b/src/device/pro.rs
> index 2e921e9..03c81e7 100644
> --- a/src/device/pro.rs
> +++ b/src/device/pro.rs
> @@ -1,10 +1,10 @@
> -// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
> +// Copyright (C) 2018-2021 Robin Krahl <robin.krahl@ireas.org>
> // SPDX-License-Identifier: MIT
>
> +use crate::backend::DeviceExt;
> use crate::device::{Device, Model, Status};
> use crate::error::Error;
> use crate::otp::GenerateOtp;
> -use crate::util::get_struct;
>
> /// A Nitrokey Pro device without user or admin authentication.
> ///
> @@ -60,9 +60,7 @@ impl<'a> Pro<'a> {
>
> impl<'a> Drop for Pro<'a> {
> fn drop(&mut self) {
> - unsafe {
> - nitrokey_sys::NK_logout();
> - }
> + self.device().logout()
> }
> }
>
> @@ -76,8 +74,10 @@ impl<'a> Device<'a> for Pro<'a> {
> }
>
> fn get_status(&self) -> Result<Status, Error> {
> - get_struct(|out| unsafe { nitrokey_sys::NK_get_status(out) })
> + self.device().get_status()
> }
> }
>
> +impl<'a> DeviceExt for Pro<'a> {}
> +
> impl<'a> GenerateOtp for Pro<'a> {}
> diff --git a/src/device/storage.rs b/src/device/storage.rs
> index ad5ca73..c1e03f3 100644
> --- a/src/device/storage.rs
> +++ b/src/device/storage.rs
> @@ -1,14 +1,14 @@
> -// Copyright (C) 2019-2020 Robin Krahl <robin.krahl@ireas.org>
> +// Copyright (C) 2019-2021 Robin Krahl <robin.krahl@ireas.org>
> // SPDX-License-Identifier: MIT
>
> -use std::convert::TryFrom as _;
> +use std::ffi;
> use std::fmt;
> use std::ops;
>
> +use crate::backend::DeviceExt;
> use crate::device::{Device, FirmwareVersion, Model, SerialNumber, Status};
> -use crate::error::{CommandError, Error};
> +use crate::error::Error;
> use crate::otp::GenerateOtp;
> -use crate::util::{get_command_result, get_cstring, get_last_error, get_struct};
>
> /// A Nitrokey Storage device without user or admin authentication.
> ///
> @@ -192,11 +192,9 @@ impl<'a> Storage<'a> {
> /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
> /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
> pub fn change_update_pin(&mut self, current: &str, new: &str) -> Result<(), Error> {
> - let current_string = get_cstring(current)?;
> - let new_string = get_cstring(new)?;
> - get_command_result(unsafe {
> - nitrokey_sys::NK_change_update_password(current_string.as_ptr(), new_string.as_ptr())
> - })
> + let current = ffi::CString::new(current)?;
> + let new = ffi::CString::new(new)?;
> + self.device().change_update_pin(¤t, &new)
> }
>
> /// Enables the firmware update mode.
> @@ -230,10 +228,8 @@ impl<'a> Storage<'a> {
> /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
> /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
> pub fn enable_firmware_update(&mut self, update_pin: &str) -> Result<(), Error> {
> - let update_pin_string = get_cstring(update_pin)?;
> - get_command_result(unsafe {
> - nitrokey_sys::NK_enable_firmware_update(update_pin_string.as_ptr())
> - })
> + let update_pin = ffi::CString::new(update_pin)?;
> + self.device().enable_firmware_update(&update_pin)
> }
>
> /// Enables the encrypted storage volume.
> @@ -265,8 +261,8 @@ impl<'a> Storage<'a> {
> /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
> /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
> pub fn enable_encrypted_volume(&mut self, user_pin: &str) -> Result<(), Error> {
> - let user_pin = get_cstring(user_pin)?;
> - get_command_result(unsafe { nitrokey_sys::NK_unlock_encrypted_volume(user_pin.as_ptr()) })
> + let user_pin = ffi::CString::new(user_pin)?;
> + self.device().enable_encrypted_volume(&user_pin)
> }
>
> /// Disables the encrypted storage volume.
> @@ -301,7 +297,7 @@ impl<'a> Storage<'a> {
> /// # }
> /// ```
> pub fn disable_encrypted_volume(&mut self) -> Result<(), Error> {
> - get_command_result(unsafe { nitrokey_sys::NK_lock_encrypted_volume() })
> + self.device().disable_encrypted_volume()
> }
>
> /// Enables a hidden storage volume.
> @@ -344,10 +340,8 @@ impl<'a> Storage<'a> {
> /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed
> /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
> pub fn enable_hidden_volume(&mut self, volume_password: &str) -> Result<(), Error> {
> - let volume_password = get_cstring(volume_password)?;
> - get_command_result(unsafe {
> - nitrokey_sys::NK_unlock_hidden_volume(volume_password.as_ptr())
> - })
> + let volume_password = ffi::CString::new(volume_password)?;
> + self.device().enable_hidden_volume(&volume_password)
> }
>
> /// Disables a hidden storage volume.
> @@ -383,7 +377,7 @@ impl<'a> Storage<'a> {
> /// # }
> /// ```
> pub fn disable_hidden_volume(&mut self) -> Result<(), Error> {
> - get_command_result(unsafe { nitrokey_sys::NK_lock_hidden_volume() })
> + self.device().disable_hidden_volume()
> }
>
> /// Creates a hidden volume.
> @@ -428,10 +422,9 @@ impl<'a> Storage<'a> {
> end: u8,
> password: &str,
> ) -> Result<(), Error> {
> - let password = get_cstring(password)?;
> - get_command_result(unsafe {
> - nitrokey_sys::NK_create_hidden_volume(slot, start, end, password.as_ptr())
> - })
> + let password = ffi::CString::new(password)?;
> + self.device()
> + .create_hidden_volume(slot, start, end, &password)
> }
>
> /// Sets the access mode of the unencrypted volume.
> @@ -469,16 +462,8 @@ impl<'a> Storage<'a> {
> admin_pin: &str,
> mode: VolumeMode,
> ) -> Result<(), Error> {
> - let admin_pin = get_cstring(admin_pin)?;
> - let result = match mode {
> - VolumeMode::ReadOnly => unsafe {
> - nitrokey_sys::NK_set_unencrypted_read_only_admin(admin_pin.as_ptr())
> - },
> - VolumeMode::ReadWrite => unsafe {
> - nitrokey_sys::NK_set_unencrypted_read_write_admin(admin_pin.as_ptr())
> - },
> - };
> - get_command_result(result)
> + let admin_pin = ffi::CString::new(admin_pin)?;
> + self.device().set_unencrypted_volume_mode(&admin_pin, mode)
> }
>
> /// Sets the access mode of the encrypted volume.
> @@ -515,16 +500,8 @@ impl<'a> Storage<'a> {
> admin_pin: &str,
> mode: VolumeMode,
> ) -> Result<(), Error> {
> - let admin_pin = get_cstring(admin_pin)?;
> - let result = match mode {
> - VolumeMode::ReadOnly => unsafe {
> - nitrokey_sys::NK_set_encrypted_read_only(admin_pin.as_ptr())
> - },
> - VolumeMode::ReadWrite => unsafe {
> - nitrokey_sys::NK_set_encrypted_read_write(admin_pin.as_ptr())
> - },
> - };
> - get_command_result(result)
> + let admin_pin = ffi::CString::new(admin_pin)?;
> + self.device().set_encrypted_volume_mode(&admin_pin, mode)
> }
>
> /// Returns the status of the connected storage device.
> @@ -549,7 +526,7 @@ impl<'a> Storage<'a> {
> /// # }
> /// ```
> pub fn get_storage_status(&self) -> Result<StorageStatus, Error> {
> - get_struct(|out| unsafe { nitrokey_sys::NK_get_status_storage(out) })
> + self.device().get_storage_status()
> }
>
> /// Returns the production information for the connected storage device.
> @@ -575,7 +552,7 @@ impl<'a> Storage<'a> {
> /// # }
> /// ```
> pub fn get_production_info(&self) -> Result<StorageProductionInfo, Error> {
> - get_struct(|out| unsafe { nitrokey_sys::NK_get_storage_production_info(out) })
> + self.device().get_production_info()
> }
>
> /// Clears the warning for a new SD card.
> @@ -608,10 +585,8 @@ impl<'a> Storage<'a> {
> /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
> /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
> pub fn clear_new_sd_card_warning(&mut self, admin_pin: &str) -> Result<(), Error> {
> - let admin_pin = get_cstring(admin_pin)?;
> - get_command_result(unsafe {
> - nitrokey_sys::NK_clear_new_sd_card_warning(admin_pin.as_ptr())
> - })
> + let admin_pin = ffi::CString::new(admin_pin)?;
> + self.device().clear_new_sd_card_warning(&admin_pin)
> }
>
> /// Returns a range of the SD card that has not been used to during this power cycle.
> @@ -631,28 +606,12 @@ 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::default();
> - let result = unsafe { nitrokey_sys::NK_get_SD_usage_data(&mut usage_data) };
> - match get_command_result(result) {
> - Ok(_) => {
> - if usage_data.write_level_min > usage_data.write_level_max
> - || usage_data.write_level_max > 100
> - {
> - Err(Error::UnexpectedError("Invalid write levels".to_owned()))
> - } else {
> - Ok(ops::Range {
> - start: usage_data.write_level_min,
> - end: usage_data.write_level_max,
> - })
> - }
> - }
> - Err(err) => Err(err),
> - }
> + self.device().get_sd_card_usage()
> }
>
> /// Blinks the red and green LED alternatively and infinitely until the device is reconnected.
> pub fn wink(&mut self) -> Result<(), Error> {
> - get_command_result(unsafe { nitrokey_sys::NK_wink() })
> + self.device().wink()
> }
>
> /// Returns the status of an ongoing background operation on the Nitrokey Storage.
> @@ -664,19 +623,7 @@ impl<'a> Storage<'a> {
> ///
> /// [`fill_sd_card`]: #method.fill_sd_card
> pub fn get_operation_status(&self) -> Result<OperationStatus, Error> {
> - let status = unsafe { nitrokey_sys::NK_get_progress_bar_value() };
> - match status {
> - 0..=100 => u8::try_from(status)
> - .map(OperationStatus::Ongoing)
> - .map_err(|_| {
> - Error::UnexpectedError("Cannot create u8 from operation status".to_owned())
> - }),
> - -1 => Ok(OperationStatus::Idle),
> - -2 => Err(get_last_error()),
> - _ => Err(Error::UnexpectedError(
> - "Invalid operation status".to_owned(),
> - )),
> - }
> + self.device().get_operation_status()
> }
>
> /// Overwrites the SD card with random data.
> @@ -714,16 +661,8 @@ impl<'a> Storage<'a> {
> /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
> /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
> pub fn fill_sd_card(&mut self, admin_pin: &str) -> Result<(), Error> {
> - let admin_pin_string = get_cstring(admin_pin)?;
> - get_command_result(unsafe {
> - nitrokey_sys::NK_fill_SD_card_with_random_data(admin_pin_string.as_ptr())
> - })
> - .or_else(|err| match err {
> - // libnitrokey’s C API returns a LongOperationInProgressException with the same error
> - // code as the WrongCrc command error, so we cannot distinguish them.
> - Error::CommandError(CommandError::WrongCrc) => Ok(()),
> - err => Err(err),
> - })
> + let admin_pin = ffi::CString::new(admin_pin)?;
> + self.device().fill_sd_card(&admin_pin)
> }
>
> /// Exports the firmware to the unencrypted volume.
> @@ -743,16 +682,14 @@ impl<'a> Storage<'a> {
> /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
> /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
> pub fn export_firmware(&mut self, admin_pin: &str) -> Result<(), Error> {
> - let admin_pin_string = get_cstring(admin_pin)?;
> - get_command_result(unsafe { nitrokey_sys::NK_export_firmware(admin_pin_string.as_ptr()) })
> + let admin_pin = ffi::CString::new(admin_pin)?;
> + self.device().export_firmware(&admin_pin)
> }
> }
>
> impl<'a> Drop for Storage<'a> {
> fn drop(&mut self) {
> - unsafe {
> - nitrokey_sys::NK_logout();
> - }
> + self.device().logout()
> }
> }
>
> @@ -773,7 +710,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 status: Status = get_struct(|out| unsafe { nitrokey_sys::NK_get_status(out) })?;
> + let mut status = self.device().get_status()?;
>
> let storage_status = self.get_storage_status()?;
> status.firmware_version = storage_status.firmware_version;
> @@ -783,56 +720,6 @@ impl<'a> Device<'a> for Storage<'a> {
> }
> }
>
> -impl<'a> GenerateOtp for Storage<'a> {}
> -
> -impl From<nitrokey_sys::NK_storage_ProductionTest> for StorageProductionInfo {
> - fn from(data: nitrokey_sys::NK_storage_ProductionTest) -> Self {
> - Self {
> - firmware_version: FirmwareVersion {
> - major: data.FirmwareVersion_au8[0],
> - minor: data.FirmwareVersion_au8[1],
> - },
> - firmware_version_internal: data.FirmwareVersionInternal_u8,
> - serial_number_cpu: data.CPU_CardID_u32,
> - sd_card: SdCardData {
> - serial_number: data.SD_CardID_u32,
> - size: data.SD_Card_Size_u8,
> - manufacturing_year: data.SD_Card_ManufacturingYear_u8,
> - manufacturing_month: data.SD_Card_ManufacturingMonth_u8,
> - oem: data.SD_Card_OEM_u16,
> - manufacturer: data.SD_Card_Manufacturer_u8,
> - },
> - }
> - }
> -}
> +impl<'a> DeviceExt for Storage<'a> {}
>
> -impl From<nitrokey_sys::NK_storage_status> for StorageStatus {
> - fn from(status: nitrokey_sys::NK_storage_status) -> Self {
> - StorageStatus {
> - unencrypted_volume: VolumeStatus {
> - read_only: status.unencrypted_volume_read_only,
> - active: status.unencrypted_volume_active,
> - },
> - encrypted_volume: VolumeStatus {
> - read_only: status.encrypted_volume_read_only,
> - active: status.encrypted_volume_active,
> - },
> - hidden_volume: VolumeStatus {
> - read_only: status.hidden_volume_read_only,
> - active: status.hidden_volume_active,
> - },
> - firmware_version: FirmwareVersion {
> - major: status.firmware_version_major,
> - minor: status.firmware_version_minor,
> - },
> - firmware_locked: status.firmware_locked,
> - serial_number_sd_card: status.serial_number_sd_card,
> - serial_number_smart_card: status.serial_number_smart_card,
> - user_retry_count: status.user_retry_count,
> - admin_retry_count: status.admin_retry_count,
> - new_sd_card_found: status.new_sd_card_found,
> - filled_with_random: status.filled_with_random,
> - stick_initialized: status.stick_initialized,
> - }
> - }
> -}
> +impl<'a> GenerateOtp for Storage<'a> {}
> diff --git a/src/device/wrapper.rs b/src/device/wrapper.rs
> index 9de0418..f9701a5 100644
> --- a/src/device/wrapper.rs
> +++ b/src/device/wrapper.rs
> @@ -1,6 +1,7 @@
> -// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
> +// Copyright (C) 2018-2021 Robin Krahl <robin.krahl@ireas.org>
> // SPDX-License-Identifier: MIT
>
> +use crate::backend::DeviceExt;
> use crate::device::{Device, Librem, Model, Pro, Status, Storage};
> use crate::error::Error;
> use crate::otp::GenerateOtp;
> @@ -153,3 +154,5 @@ impl<'a> Device<'a> for DeviceWrapper<'a> {
> }
> }
> }
> +
> +impl<'a> DeviceExt for DeviceWrapper<'a> {}
> diff --git a/src/error.rs b/src/error.rs
> index f1e91c3..3908b8a 100644
> --- a/src/error.rs
> +++ b/src/error.rs
> @@ -1,7 +1,8 @@
> -// Copyright (C) 2019 Robin Krahl <robin.krahl@ireas.org>
> +// Copyright (C) 2019-2021 Robin Krahl <robin.krahl@ireas.org>
> // SPDX-License-Identifier: MIT
>
> use std::error;
> +use std::ffi;
> use std::fmt;
> use std::os::raw;
> use std::str;
> @@ -65,6 +66,12 @@ impl From<LibraryError> for Error {
> }
> }
>
> +impl From<ffi::NulError> for Error {
> + fn from(_error: ffi::NulError) -> Self {
> + LibraryError::InvalidString.into()
> + }
> +}
> +
> impl From<str::Utf8Error> for Error {
> fn from(error: str::Utf8Error) -> Self {
> Error::Utf8Error(error)
> diff --git a/src/lib.rs b/src/lib.rs
> index 6e9d252..d53db83 100644
> --- a/src/lib.rs
> +++ b/src/lib.rs
> @@ -1,4 +1,4 @@
> -// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
> +// Copyright (C) 2018-2022 Robin Krahl <robin.krahl@ireas.org>
> // SPDX-License-Identifier: MIT
>
> //! Provides access to a Nitrokey device using the native libnitrokey API.
> @@ -116,11 +116,13 @@
> //! [`WrongCrc`]: enum.CommandError.html#variant.WrongCrc
>
> #![warn(missing_docs, rust_2018_compatibility, rust_2018_idioms, unused)]
> +#![deny(unsafe_code)]
>
> #[macro_use(lazy_static)]
> extern crate lazy_static;
>
> mod auth;
> +mod backend;
> mod config;
> mod device;
> mod error;
> @@ -128,10 +130,9 @@ mod otp;
> mod pws;
> mod util;
>
> -use std::convert::TryInto as _;
> +use std::ffi;
> use std::fmt;
> use std::marker;
> -use std::ptr::NonNull;
> use std::sync;
>
> pub use crate::auth::{Admin, Authenticate, User};
> @@ -146,8 +147,6 @@ pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData};
> pub use crate::pws::{GetPasswordSafe, PasswordSafe, PasswordSlot};
> pub use crate::util::LogLevel;
>
> -use crate::util::{get_cstring, get_last_result};
> -
> /// The number of slots in a [`PasswordSafe`][].
> ///
> /// This constant is deprecated. Use [`PasswordSafe::get_slot_count`][] instead.
> @@ -286,11 +285,8 @@ impl Manager {
> /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
> /// [`UnsupportedModelError`]: enum.Error.html#variant.UnsupportedModelError
> pub fn connect(&mut self) -> Result<DeviceWrapper<'_>, Error> {
> - if unsafe { nitrokey_sys::NK_login_auto() } == 1 {
> - device::get_connected_device(self)
> - } else {
> - Err(CommunicationError::NotConnected.into())
> - }
> + let model = backend::connect()?;
> + Ok(device::create_device_wrapper(self, model))
> }
>
> /// Connects to a Nitrokey device of the given model.
> @@ -316,11 +312,8 @@ impl Manager {
> ///
> /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
> pub fn connect_model(&mut self, model: Model) -> Result<DeviceWrapper<'_>, Error> {
> - if device::connect_enum(model) {
> - Ok(device::create_device_wrapper(self, model))
> - } else {
> - Err(CommunicationError::NotConnected.into())
> - }
> + backend::connect_model(model)?;
> + Ok(device::create_device_wrapper(self, model))
> }
>
> /// Connects to a Nitrokey device at the given USB path.
> @@ -358,12 +351,9 @@ impl Manager {
> /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
> /// [`UnsupportedModelError`]: enum.Error.html#variant.UnsupportedModelError
> pub fn connect_path<S: Into<Vec<u8>>>(&mut self, path: S) -> Result<DeviceWrapper<'_>, Error> {
> - let path = get_cstring(path)?;
> - if unsafe { nitrokey_sys::NK_connect_with_path(path.as_ptr()) } == 1 {
> - device::get_connected_device(self)
> - } else {
> - Err(CommunicationError::NotConnected.into())
> - }
> + let path = ffi::CString::new(path)?;
> + let model = backend::connect_path(&path)?;
> + Ok(device::create_device_wrapper(self, model))
> }
>
> /// Connects to a Librem Key.
> @@ -388,11 +378,8 @@ impl Manager {
> ///
> /// [`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())
> - }
> + backend::connect_model(Model::Librem)?;
> + Ok(device::Librem::new(self))
> }
>
> /// Connects to a Nitrokey Pro.
> @@ -417,11 +404,8 @@ impl Manager {
> ///
> /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
> pub fn connect_pro(&mut self) -> Result<Pro<'_>, Error> {
> - if device::connect_enum(device::Model::Pro) {
> - Ok(device::Pro::new(self))
> - } else {
> - Err(CommunicationError::NotConnected.into())
> - }
> + backend::connect_model(Model::Pro)?;
> + Ok(device::Pro::new(self))
> }
>
> /// Connects to a Nitrokey Storage.
> @@ -446,11 +430,8 @@ impl Manager {
> ///
> /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
> pub fn connect_storage(&mut self) -> Result<Storage<'_>, Error> {
> - if device::connect_enum(Model::Storage) {
> - Ok(Storage::new(self))
> - } else {
> - Err(CommunicationError::NotConnected.into())
> - }
> + backend::connect_model(Model::Storage)?;
> + Ok(Storage::new(self))
> }
> }
>
> @@ -556,29 +537,7 @@ pub fn force_take() -> Result<sync::MutexGuard<'static, Manager>, Error> {
> /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
> /// [`Utf8Error`]: enum.Error.html#variant.Utf8Error
> pub fn list_devices() -> Result<Vec<DeviceInfo>, Error> {
> - let ptr = NonNull::new(unsafe { nitrokey_sys::NK_list_devices() });
> - match ptr {
> - Some(mut ptr) => {
> - let mut vec: Vec<DeviceInfo> = Vec::new();
> - push_device_info(&mut vec, unsafe { ptr.as_ref() })?;
> - unsafe {
> - nitrokey_sys::NK_free_device_info(ptr.as_mut());
> - }
> - Ok(vec)
> - }
> - None => get_last_result().map(|_| Vec::new()),
> - }
> -}
> -
> -fn push_device_info(
> - vec: &mut Vec<DeviceInfo>,
> - info: &nitrokey_sys::NK_device_info,
> -) -> Result<(), Error> {
> - vec.push(info.try_into()?);
> - if let Some(ptr) = NonNull::new(info.next) {
> - push_device_info(vec, unsafe { ptr.as_ref() })?;
> - }
> - Ok(())
> + backend::list_devices()
> }
>
> /// Enables or disables debug output. Calling this method with `true` is equivalent to setting the
> @@ -590,17 +549,13 @@ fn push_device_info(
> ///
> /// [`set_log_level`]: fn.set_log_level.html
> pub fn set_debug(state: bool) {
> - unsafe {
> - nitrokey_sys::NK_set_debug(state);
> - }
> + backend::set_debug(state)
> }
>
> /// Sets the log level for libnitrokey. All log messages are written to the standard error stream.
> /// Setting the log level enables all log messages on the same or on a higher log level.
> pub fn set_log_level(level: LogLevel) {
> - unsafe {
> - nitrokey_sys::NK_set_debug_level(level.into());
> - }
> + backend::set_log_level(level)
> }
>
> /// Returns the libnitrokey library version.
> @@ -619,14 +574,5 @@ pub fn set_log_level(level: LogLevel) {
> ///
> /// [`Utf8Error`]: enum.Error.html#variant.Utf8Error
> pub fn get_library_version() -> Result<Version, Error> {
> - // NK_get_library_version returns a static string, so we don’t have to free the pointer.
> - let git = unsafe { nitrokey_sys::NK_get_library_version() };
> - let git = if git.is_null() {
> - String::new()
> - } else {
> - util::owned_str_from_ptr(git)?
> - };
> - let major = unsafe { nitrokey_sys::NK_get_major_library_version() };
> - let minor = unsafe { nitrokey_sys::NK_get_minor_library_version() };
> - Ok(Version { git, major, minor })
> + backend::get_library_version()
> }
> diff --git a/src/otp.rs b/src/otp.rs
> index 7b47a3b..37e5c0a 100644
> --- a/src/otp.rs
> +++ b/src/otp.rs
> @@ -1,10 +1,10 @@
> -// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
> +// Copyright (C) 2018-2021 Robin Krahl <robin.krahl@ireas.org>
> // SPDX-License-Identifier: MIT
>
> use std::ffi::CString;
>
> +use crate::backend;
> use crate::error::Error;
> -use crate::util::{get_command_result, get_cstring, result_from_string};
>
> /// Modes for one-time password generation.
> #[derive(Clone, Copy, Debug, PartialEq)]
> @@ -156,7 +156,7 @@ pub trait ConfigureOtp {
>
> /// Provides methods to generate OTP codes and to query OTP slots on a Nitrokey
> /// device.
> -pub trait GenerateOtp {
> +pub trait GenerateOtp: backend::DeviceExt {
> /// Sets the time on the Nitrokey.
> ///
> /// `time` is the number of seconds since January 1st, 1970 (Unix timestamp). Unless `force`
> @@ -191,12 +191,7 @@ pub trait GenerateOtp {
> /// [`get_totp_code`]: #method.get_totp_code
> /// [`Timestamp`]: enum.CommandError.html#variant.Timestamp
> fn set_time(&mut self, time: u64, force: bool) -> Result<(), Error> {
> - let result = if force {
> - unsafe { nitrokey_sys::NK_totp_set_time(time) }
> - } else {
> - unsafe { nitrokey_sys::NK_totp_set_time_soft(time) }
> - };
> - get_command_result(result)
> + self.device().set_time(time, force)
> }
>
> /// Returns the name of the given HOTP slot.
> @@ -226,7 +221,7 @@ pub trait GenerateOtp {
> /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
> /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
> fn get_hotp_slot_name(&self, slot: u8) -> Result<String, Error> {
> - result_from_string(unsafe { nitrokey_sys::NK_get_hotp_slot_name(slot) })
> + self.device().get_hotp_slot_name(slot)
> }
>
> /// Returns the name of the given TOTP slot.
> @@ -256,7 +251,7 @@ pub trait GenerateOtp {
> /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
> /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
> fn get_totp_slot_name(&self, slot: u8) -> Result<String, Error> {
> - result_from_string(unsafe { nitrokey_sys::NK_get_totp_slot_name(slot) })
> + self.device().get_totp_slot_name(slot)
> }
>
> /// Generates an HOTP code on the given slot. This operation may require user authorization,
> @@ -288,7 +283,7 @@ pub trait GenerateOtp {
> /// [`NotAuthorized`]: enum.CommandError.html#variant.NotAuthorized
> /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
> fn get_hotp_code(&mut self, slot: u8) -> Result<String, Error> {
> - result_from_string(unsafe { nitrokey_sys::NK_get_hotp_code(slot) })
> + self.device().get_hotp_code(slot)
> }
>
> /// Generates a TOTP code on the given slot. This operation may require user authorization,
> @@ -332,7 +327,7 @@ pub trait GenerateOtp {
> /// [`NotAuthorized`]: enum.CommandError.html#variant.NotAuthorized
> /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
> fn get_totp_code(&self, slot: u8) -> Result<String, Error> {
> - result_from_string(unsafe { nitrokey_sys::NK_get_totp_code(slot, 0, 0, 0) })
> + self.device().get_totp_code(slot)
> }
> }
>
> @@ -403,10 +398,10 @@ impl OtpSlotData {
>
> impl RawOtpSlotData {
> pub fn new(data: OtpSlotData) -> Result<RawOtpSlotData, Error> {
> - let name = get_cstring(data.name)?;
> - let secret = get_cstring(data.secret)?;
> + let name = CString::new(data.name)?;
> + let secret = CString::new(data.secret)?;
> let use_token_id = data.token_id.is_some();
> - let token_id = get_cstring(data.token_id.unwrap_or_else(String::new))?;
> + let token_id = CString::new(data.token_id.unwrap_or_else(String::new))?;
>
> Ok(RawOtpSlotData {
> number: data.number,
> diff --git a/src/pws.rs b/src/pws.rs
> index 83cb3e4..aee1c4c 100644
> --- a/src/pws.rs
> +++ b/src/pws.rs
> @@ -1,9 +1,10 @@
> -// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
> +// Copyright (C) 2018-2021 Robin Krahl <robin.krahl@ireas.org>
> // SPDX-License-Identifier: MIT
>
> +use std::ffi;
> +
> use crate::device::{Device, DeviceWrapper, Librem, Pro, Storage};
> use crate::error::{CommandError, Error, LibraryError};
> -use crate::util::{get_command_result, get_cstring, get_last_error, result_from_string};
>
> const SLOT_COUNT: u8 = 16;
>
> @@ -53,7 +54,7 @@ const SLOT_COUNT: u8 = 16;
> /// [`GetPasswordSafe`]: trait.GetPasswordSafe.html
> #[derive(Debug)]
> pub struct PasswordSafe<'a, 'b> {
> - _device: &'a dyn Device<'b>,
> + device: &'a dyn Device<'b>,
> }
>
> /// A slot of a [`PasswordSafe`][].
> @@ -62,7 +63,7 @@ pub struct PasswordSafe<'a, 'b> {
> #[derive(Clone, Copy, Debug)]
> pub struct PasswordSlot<'p, 'a, 'b> {
> slot: u8,
> - _pws: &'p PasswordSafe<'a, 'b>,
> + pws: &'p PasswordSafe<'a, 'b>,
> }
>
> /// Provides access to a [`PasswordSafe`][].
> @@ -129,9 +130,11 @@ fn get_password_safe<'a, 'b>(
> device: &'a dyn Device<'b>,
> user_pin: &str,
> ) -> 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 })
> + let user_pin = ffi::CString::new(user_pin)?;
> + device
> + .device()
> + .enable_password_safe(&user_pin)
> + .map(|_| PasswordSafe { device })
> }
>
> fn get_pws_result(s: String) -> Result<String, Error> {
> @@ -178,20 +181,7 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
> /// [`get_slots`]: #method.get_slots
> #[deprecated(since = "0.9.0", note = "Use get_slots() instead")]
> pub fn get_slot_status(&self) -> Result<[bool; 16], Error> {
> - 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 {
> - nitrokey_sys::NK_free_password_safe_slot_status(status_ptr);
> - }
> - Ok(result)
> + self.device.device().get_pws_slot_status()
> }
>
> /// Returns all password slots.
> @@ -222,23 +212,15 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
> ///
> /// [`PasswordSlot`]: struct.PasswordSlot.html
> pub fn get_slots(&self) -> Result<Vec<Option<PasswordSlot<'_, 'a, 'b>>>, Error> {
> + let slot_status = self.device.device().get_pws_slot_status()?;
> 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 }));
> + if slot_status[usize::from(slot)] {
> + slots.push(Some(PasswordSlot { slot, pws: self }));
> } else {
> slots.push(None);
> }
> }
> - unsafe {
> - nitrokey_sys::NK_free_password_safe_slot_status(status_ptr);
> - }
> Ok(slots)
> }
>
> @@ -317,7 +299,7 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
> /// [`get_slots`]: #method.get_slots
> pub fn get_slot_unchecked(&self, slot: u8) -> Result<PasswordSlot<'_, 'a, 'b>, Error> {
> if slot < self.get_slot_count() {
> - Ok(PasswordSlot { slot, _pws: self })
> + Ok(PasswordSlot { slot, pws: self })
> } else {
> Err(LibraryError::InvalidSlot.into())
> }
> @@ -367,7 +349,9 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
> /// [`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) })
> + self.device
> + .device()
> + .get_pws_slot_name(slot)
> .and_then(get_pws_result)
> }
>
> @@ -411,7 +395,9 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
> /// [`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) })
> + self.device
> + .device()
> + .get_pws_slot_login(slot)
> .and_then(get_pws_result)
> }
>
> @@ -455,7 +441,9 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
> /// [`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) })
> + self.device
> + .device()
> + .get_pws_slot_password(slot)
> .and_then(get_pws_result)
> }
>
> @@ -490,17 +478,12 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
> login: &str,
> password: &str,
> ) -> Result<(), Error> {
> - let name_string = get_cstring(name)?;
> - let login_string = get_cstring(login)?;
> - let password_string = get_cstring(password)?;
> - get_command_result(unsafe {
> - nitrokey_sys::NK_write_password_safe_slot(
> - slot,
> - name_string.as_ptr(),
> - login_string.as_ptr(),
> - password_string.as_ptr(),
> - )
> - })
> + let name = ffi::CString::new(name)?;
> + let login = ffi::CString::new(login)?;
> + let password = ffi::CString::new(password)?;
> + self.device
> + .device()
> + .write_pws_slot(slot, &name, &login, &password)
> }
>
> /// Erases the given slot. Erasing clears the stored name, login and password (if the slot was
> @@ -530,7 +513,7 @@ impl<'a, 'b> PasswordSafe<'a, 'b> {
> ///
> /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot
> pub fn erase_slot(&mut self, slot: u8) -> Result<(), Error> {
> - get_command_result(unsafe { nitrokey_sys::NK_erase_password_safe_slot(slot) })
> + self.device.device().erase_pws_slot(slot)
> }
> }
>
> @@ -549,17 +532,17 @@ impl<'p, 'a, 'b> PasswordSlot<'p, 'a, 'b> {
>
> /// 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) })
> + self.pws.device.device().get_pws_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) })
> + self.pws.device.device().get_pws_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) })
> + self.pws.device.device().get_pws_slot_password(self.slot)
> }
> }
>
> diff --git a/src/util.rs b/src/util.rs
> index 89a8b46..2a76ac7 100644
> --- a/src/util.rs
> +++ b/src/util.rs
> @@ -1,13 +1,11 @@
> -// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
> +// Copyright (C) 2018-2021 Robin Krahl <robin.krahl@ireas.org>
> // SPDX-License-Identifier: MIT
>
> -use std::ffi::{CStr, CString};
> -use std::os::raw::{c_char, c_int};
> +use std::ffi::CString;
>
> -use libc::{c_void, free};
> use rand_core::{OsRng, RngCore};
>
> -use crate::error::{Error, LibraryError};
> +use crate::error::Error;
>
> /// Log level for libnitrokey.
> ///
> @@ -30,73 +28,6 @@ pub enum LogLevel {
> DebugL2,
> }
>
> -pub fn str_from_ptr<'a>(ptr: *const c_char) -> Result<&'a str, Error> {
> - unsafe { CStr::from_ptr(ptr) }.to_str().map_err(Error::from)
> -}
> -
> -pub fn owned_str_from_ptr(ptr: *const c_char) -> Result<String, Error> > - str_from_ptr(ptr).map(ToOwned::to_owned)
> -}
> -
> -pub fn run_with_string<R, F>(ptr: *const c_char, op: F) -> Result<R, Error>
> -where
> - F: FnOnce(&str) -> Result<R, Error>,
> -{
> - if ptr.is_null() {
> - return Err(Error::UnexpectedError(
> - "libnitrokey returned a null pointer".to_owned(),
> - ));
> - }
> - let result = str_from_ptr(ptr).and_then(op);
> - unsafe { free(ptr as *mut c_void) };
> - result
> -}
> -
> -pub fn result_from_string(ptr: *const c_char) -> Result<String, Error> {
> - run_with_string(ptr, |s| {
> - // An empty string can both indicate an error or be a valid return value. In this case, we
> - // have to check the last command status to decide what to return.
> - if s.is_empty() {
> - get_last_result().map(|_| s.to_owned())
> - } else {
> - Ok(s.to_owned())
> - }
> - })
> -}
> -
> -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(())
> - } else {
> - Err(Error::from(value))
> - }
> -}
> -
> -pub fn get_last_result() -> Result<(), Error> {
> - get_command_result(unsafe { nitrokey_sys::NK_get_last_command_status() }.into())
> -}
> -
> -pub fn get_last_error() -> Error {
> - get_last_result().err().unwrap_or_else(|| {
> - Error::UnexpectedError("Expected an error, but command status is zero".to_owned())
> - })
> -}
> -
> pub fn generate_password(length: usize) -> Result<CString, Error> {
> loop {
> // Randomly generate a password until we get a string *without* null bytes. Otherwise
> @@ -109,10 +40,6 @@ pub fn generate_password(length: usize) -> Result<CString, Error> {
> }
> }
>
> -pub fn get_cstring<T: Into<Vec<u8>>>(s: T) -> Result<CString, Error> {
> - CString::new(s).map_err(|_| LibraryError::InvalidString.into())
> -}
> -
> impl From<LogLevel> for i32 {
> fn from(log_level: LogLevel) -> i32 {
> match log_level {