diff options
-rw-r--r-- | .builds/archlinux-msrv.yml | 2 | ||||
-rw-r--r-- | CHANGELOG.md | 17 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | src/auth.rs | 13 | ||||
-rw-r--r-- | src/config.rs | 52 | ||||
-rw-r--r-- | src/device/mod.rs | 20 | ||||
-rw-r--r-- | src/device/pro.rs | 2 | ||||
-rw-r--r-- | src/device/storage.rs | 2 | ||||
-rw-r--r-- | src/device/wrapper.rs | 1 | ||||
-rw-r--r-- | src/error.rs | 57 | ||||
-rw-r--r-- | src/lib.rs | 17 | ||||
-rw-r--r-- | src/otp.rs | 10 | ||||
-rw-r--r-- | src/pws.rs | 3 | ||||
-rw-r--r-- | src/util.rs | 2 | ||||
-rw-r--r-- | tests/device.rs | 36 |
16 files changed, 122 insertions, 118 deletions
diff --git a/.builds/archlinux-msrv.yml b/.builds/archlinux-msrv.yml index 66c0390..b98a5dc 100644 --- a/.builds/archlinux-msrv.yml +++ b/.builds/archlinux-msrv.yml @@ -11,7 +11,7 @@ sources: tasks: - setup: | rustup set profile minimal - rustup default 1.34.2 + rustup default 1.40.0 - version: | rustc -V - build: | diff --git a/CHANGELOG.md b/CHANGELOG.md index e2dd8a7..275ea81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,23 @@ Copyright (C) 2019-2020 Robin Krahl <robin.krahl@ireas.org> SPDX-License-Identifier: CC0-1.0 --> +# Unreleased +- Export the `FirmwareVersion` struct. +- Mark the `Error`, `CommandError`, `CommunicationError`, `LibraryError`, + `Model` and `DeviceWrapper` enums as non-exhaustive. + - Bump the MSRV to 1.40.0. +- Rename the `numlock`, `capslock`, `scrollock` fields of the `Config` struct + to `num_lock`, `caps_lock`, `scroll_lock`. + +# v0.7.1 (2020-08-30) +- Remove the custom `std::error::Error::source` implementation for + `error::Error` to avoid duplicate error messages. + +# v0.7.0 (2020-07-14) +- Refactor the `Error` enum so that it is `Send`, `Sync` and `'static`: + - Remove the `sync::PoisonError` from the `PoisonError` variant. + - Remove `Error::RandError` variant. + # v0.6.0 (2020-02-03) - Add `String` value to the `Error::UnexpectedError` variant. - Always store serial numbers as structs: @@ -3,7 +3,7 @@ [package] name = "nitrokey" -version = "0.6.0" +version = "0.7.1" authors = ["Robin Krahl <robin.krahl@ireas.org>"] edition = "2018" homepage = "https://code.ireas.org/nitrokey-rs/" @@ -85,7 +85,7 @@ development of this crate. Thanks to Daniel Mueller for contributions to ## Minimum Supported Rust Version -This crate supports Rust 1.34.2 or later. +This crate supports Rust 1.40.0 or later. ## Contact @@ -103,7 +103,7 @@ in the `LICENSES` directory. `libnitrokey` is licensed under the [LGPL-3.0][]. [API reference]: https://docs.rs/nitrokey [examples]: https://git.ireas.org/nitrokey-rs/tree/examples -[`nitrocli`]: https://github.com/d-e-s-o/nitrocli/tree/master/nitrocli +[`nitrocli`]: https://github.com/d-e-s-o/nitrocli [Nitrokey udev rules]: https://www.nitrokey.com/documentation/frequently-asked-questions-faq#openpgp-card-not-available [`libnitrokey`]: https://github.com/nitrokey/libnitrokey [`nitrokey-test`]: https://github.com/d-e-s-o/nitrokey-test diff --git a/src/auth.rs b/src/auth.rs index 6748ca1..5ca59da 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -8,8 +8,6 @@ use std::ops; use std::os::raw::c_char; use std::os::raw::c_int; -use nitrokey_sys; - use crate::config::{Config, RawConfig}; use crate::device::{Device, DeviceWrapper, Pro, Storage}; use crate::error::Error; @@ -128,7 +126,7 @@ trait AuthenticatedDevice<T> { /// method. /// /// [`Authenticate`]: trait.Authenticate.html -/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin +/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user /// [`device`]: #method.device #[derive(Debug)] pub struct User<'a, T: Device<'a>> { @@ -280,7 +278,8 @@ impl<'a, T: Device<'a>> Admin<'a, T> { /// /// # Errors /// - /// - [`InvalidSlot`][] if the provided numlock, capslock or scrolllock slot is larger than two + /// - [`InvalidSlot`][] if the provided Num Lock, Caps Lock or Scroll Lock slot is larger than + /// two /// /// # Example /// @@ -308,9 +307,9 @@ impl<'a, T: Device<'a>> Admin<'a, T> { let raw_config = RawConfig::try_from(config)?; get_command_result(unsafe { nitrokey_sys::NK_write_config( - raw_config.numlock, - raw_config.capslock, - raw_config.scrollock, + raw_config.num_lock, + raw_config.caps_lock, + raw_config.scroll_lock, raw_config.user_password, false, self.temp_password.as_ptr(), diff --git a/src/config.rs b/src/config.rs index 120a51b..bc935d7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,15 +8,15 @@ use crate::error::{Error, LibraryError}; /// The configuration for a Nitrokey. #[derive(Clone, Copy, Debug, PartialEq)] pub struct Config { - /// If set, the stick will generate a code from the HOTP slot with the given number if numlock + /// If set, the stick will generate a code from the HOTP slot with the given number if Num Lock /// is pressed. The slot number must be 0, 1 or 2. - pub numlock: Option<u8>, - /// If set, the stick will generate a code from the HOTP slot with the given number if capslock - /// is pressed. The slot number must be 0, 1 or 2. - pub capslock: Option<u8>, - /// If set, the stick will generate a code from the HOTP slot with the given number if - /// scrollock is pressed. The slot number must be 0, 1 or 2. - pub scrollock: Option<u8>, + pub num_lock: Option<u8>, + /// If set, the stick will generate a code from the HOTP slot with the given number if Caps + /// Lock is pressed. The slot number must be 0, 1 or 2. + pub caps_lock: Option<u8>, + /// If set, the stick will generate a code from the HOTP slot with the given number if Scroll + /// Lock is pressed. The slot number must be 0, 1 or 2. + pub scroll_lock: Option<u8>, /// If set, OTP generation using [`get_hotp_code`][] or [`get_totp_code`][] requires user /// authentication. Otherwise, OTPs can be generated without authentication. /// @@ -27,9 +27,9 @@ pub struct Config { #[derive(Debug)] pub struct RawConfig { - pub numlock: u8, - pub capslock: u8, - pub scrollock: u8, + pub num_lock: u8, + pub caps_lock: u8, + pub scroll_lock: u8, pub user_password: bool, } @@ -56,15 +56,15 @@ fn option_to_config_otp_slot(value: Option<u8>) -> Result<u8, Error> { impl Config { /// Constructs a new instance of this struct. pub fn new( - numlock: Option<u8>, - capslock: Option<u8>, - scrollock: Option<u8>, + num_lock: Option<u8>, + caps_lock: Option<u8>, + scroll_lock: Option<u8>, user_password: bool, ) -> Config { Config { - numlock, - capslock, - scrollock, + num_lock, + caps_lock, + scroll_lock, user_password, } } @@ -75,9 +75,9 @@ impl convert::TryFrom<Config> for RawConfig { fn try_from(config: Config) -> Result<RawConfig, Error> { Ok(RawConfig { - numlock: option_to_config_otp_slot(config.numlock)?, - capslock: option_to_config_otp_slot(config.capslock)?, - scrollock: option_to_config_otp_slot(config.scrollock)?, + 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, }) } @@ -86,9 +86,9 @@ impl convert::TryFrom<Config> for RawConfig { impl From<&nitrokey_sys::NK_status> for RawConfig { fn from(status: &nitrokey_sys::NK_status) -> Self { Self { - numlock: status.config_numlock, - capslock: status.config_capslock, - scrollock: status.config_scrolllock, + num_lock: status.config_numlock, + caps_lock: status.config_capslock, + scroll_lock: status.config_scrolllock, user_password: status.otp_user_password, } } @@ -97,9 +97,9 @@ impl From<&nitrokey_sys::NK_status> for RawConfig { impl Into<Config> for RawConfig { fn into(self) -> Config { Config { - numlock: config_otp_slot_to_option(self.numlock), - capslock: config_otp_slot_to_option(self.capslock), - scrollock: config_otp_slot_to_option(self.scrollock), + 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, } } diff --git a/src/device/mod.rs b/src/device/mod.rs index 067fdf6..7fec18b 100644 --- a/src/device/mod.rs +++ b/src/device/mod.rs @@ -10,8 +10,6 @@ use std::ffi; use std::fmt; use std::str; -use nitrokey_sys; - use crate::auth::Authenticate; use crate::config::{Config, RawConfig}; use crate::error::{CommunicationError, Error, LibraryError}; @@ -30,6 +28,7 @@ pub use wrapper::DeviceWrapper; /// Available Nitrokey models. #[derive(Clone, Copy, Debug, PartialEq)] +#[non_exhaustive] pub enum Model { /// The Nitrokey Storage. Storage, @@ -225,9 +224,8 @@ fn get_hidapi_serial_number(serial_number: &str) -> Option<SerialNumber> { return None; } - let iter = serial_number.char_indices().rev(); - let first_non_null = iter.skip_while(|(_, c)| *c == '0').next(); - if let Some((i, _)) = first_non_null { + 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 @@ -307,6 +305,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// } /// # Ok::<(), nitrokey::Error>(()) /// ``` + /// + /// [`Manager`]: struct.Manager.html fn into_manager(self) -> &'a mut crate::Manager; /// Returns the model of the connected Nitrokey device. @@ -464,9 +464,9 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// let mut manager = nitrokey::take()?; /// let device = manager.connect()?; /// let config = device.get_config()?; - /// println!("numlock binding: {:?}", config.numlock); - /// println!("capslock binding: {:?}", config.capslock); - /// println!("scrollock binding: {:?}", config.scrollock); + /// println!("Num Lock binding: {:?}", config.num_lock); + /// println!("Caps Lock binding: {:?}", config.caps_lock); + /// println!("Scroll Lock binding: {:?}", config.scroll_lock); /// println!("require password for OTP: {:?}", config.user_password); /// # Ok(()) /// # } @@ -644,6 +644,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// # } /// ``` /// + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString + /// [`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)?; @@ -679,6 +681,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// # } /// ``` /// + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString + /// [`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)?; diff --git a/src/device/pro.rs b/src/device/pro.rs index 591b730..0d5443e 100644 --- a/src/device/pro.rs +++ b/src/device/pro.rs @@ -1,8 +1,6 @@ // Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org> // SPDX-License-Identifier: MIT -use nitrokey_sys; - use crate::device::{Device, Model, Status}; use crate::error::Error; use crate::otp::GenerateOtp; diff --git a/src/device/storage.rs b/src/device/storage.rs index 5669a91..a18d94f 100644 --- a/src/device/storage.rs +++ b/src/device/storage.rs @@ -5,8 +5,6 @@ use std::convert::TryFrom as _; use std::fmt; use std::ops; -use nitrokey_sys; - use crate::device::{Device, FirmwareVersion, Model, SerialNumber, Status}; use crate::error::{CommandError, Error}; use crate::otp::GenerateOtp; diff --git a/src/device/wrapper.rs b/src/device/wrapper.rs index 69291ad..942a905 100644 --- a/src/device/wrapper.rs +++ b/src/device/wrapper.rs @@ -64,6 +64,7 @@ use crate::otp::GenerateOtp; /// /// [`connect`]: struct.Manager.html#method.connect #[derive(Debug)] +#[non_exhaustive] pub enum DeviceWrapper<'a> { /// A Nitrokey Storage device. Storage(Storage<'a>), diff --git a/src/error.rs b/src/error.rs index 7bea3f2..f1e91c3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -11,6 +11,7 @@ use crate::device; /// An error returned by the nitrokey crate. #[derive(Debug)] +#[non_exhaustive] pub enum Error { /// An error reported by the Nitrokey device in the response packet. CommandError(CommandError), @@ -21,9 +22,7 @@ pub enum Error { /// A library usage error. LibraryError(LibraryError), /// An error that occurred due to a poisoned lock. - PoisonError(sync::PoisonError<sync::MutexGuard<'static, crate::Manager>>), - /// An error that occurred during random number generation. - RandError(Box<dyn error::Error>), + PoisonError, /// An error that is caused by an unexpected value returned by libnitrokey. UnexpectedError(String), /// An unknown error returned by libnitrokey. @@ -72,14 +71,14 @@ impl From<str::Utf8Error> for Error { } } -impl From<sync::PoisonError<sync::MutexGuard<'static, crate::Manager>>> for Error { - fn from(error: sync::PoisonError<sync::MutexGuard<'static, crate::Manager>>) -> Self { - Error::PoisonError(error) +impl<T> From<sync::PoisonError<T>> for Error { + fn from(_error: sync::PoisonError<T>) -> Self { + Error::PoisonError } } -impl From<sync::TryLockError<sync::MutexGuard<'static, crate::Manager>>> for Error { - fn from(error: sync::TryLockError<sync::MutexGuard<'static, crate::Manager>>) -> Self { +impl<T> From<sync::TryLockError<T>> for Error { + fn from(error: sync::TryLockError<T>) -> Self { match error { sync::TryLockError::Poisoned(err) => err.into(), sync::TryLockError::WouldBlock => Error::ConcurrentAccessError, @@ -93,22 +92,7 @@ impl<'a, T: device::Device<'a>> From<(T, Error)> for Error { } } -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match *self { - Error::CommandError(ref err) => Some(err), - Error::CommunicationError(ref err) => Some(err), - Error::ConcurrentAccessError => None, - Error::LibraryError(ref err) => Some(err), - Error::PoisonError(ref err) => Some(err), - Error::RandError(ref err) => Some(err.as_ref()), - Error::UnexpectedError(_) => None, - Error::UnknownError(_) => None, - Error::UnsupportedModelError => None, - Error::Utf8Error(ref err) => Some(err), - } - } -} +impl error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -117,8 +101,7 @@ impl fmt::Display for Error { Error::CommunicationError(ref err) => write!(f, "Communication error: {}", err), Error::ConcurrentAccessError => write!(f, "Internal error: concurrent access"), Error::LibraryError(ref err) => write!(f, "Library error: {}", err), - Error::PoisonError(_) => write!(f, "Internal error: poisoned lock"), - Error::RandError(ref err) => write!(f, "RNG error: {}", err), + Error::PoisonError => write!(f, "Internal error: poisoned lock"), Error::UnexpectedError(ref s) => write!(f, "An unexpected error occurred: {}", s), Error::UnknownError(ref err) => write!(f, "Unknown error: {}", err), Error::UnsupportedModelError => write!(f, "Unsupported Nitrokey model"), @@ -129,6 +112,7 @@ impl fmt::Display for Error { /// An error reported by the Nitrokey device in the response packet. #[derive(Clone, Copy, Debug, PartialEq)] +#[non_exhaustive] pub enum CommandError { /// A packet with a wrong checksum has been sent or received. WrongCrc, @@ -195,6 +179,7 @@ impl fmt::Display for CommandError { /// A device communication error. #[derive(Clone, Copy, Debug, PartialEq)] +#[non_exhaustive] pub enum CommunicationError { /// Could not connect to a Nitrokey device. NotConnected, @@ -233,6 +218,7 @@ impl fmt::Display for CommunicationError { /// A library usage error. #[derive(Clone, Copy, Debug, PartialEq)] +#[non_exhaustive] pub enum LibraryError { /// A supplied string exceeded a length limit. StringTooLong, @@ -271,3 +257,22 @@ impl fmt::Display for LibraryError { }) } } + +// build our own static assertion that Error implements error::Error, Send, Sync, 'static + +struct Helper<T>(T); + +trait Assert { + fn assert() -> bool; +} + +impl<T: error::Error + Send + Sync + 'static> Assert for Helper<T> { + fn assert() -> bool { + true + } +} + +#[allow(unused)] +fn assert_error_impl() { + let _ = Helper::<Error>::assert(); +} @@ -133,13 +133,11 @@ use std::marker; use std::ptr::NonNull; use std::sync; -use nitrokey_sys; - pub use crate::auth::{Admin, Authenticate, User}; pub use crate::config::Config; pub use crate::device::{ - Device, DeviceInfo, DeviceWrapper, Model, OperationStatus, Pro, SdCardData, SerialNumber, - Status, Storage, StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus, + Device, DeviceInfo, DeviceWrapper, FirmwareVersion, 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}; @@ -472,13 +470,10 @@ pub fn take() -> Result<sync::MutexGuard<'static, Manager>, Error> { /// [`ConcurrentAccessError`]: struct.Error.html#variant.ConcurrentAccessError /// [`Manager`]: struct.Manager.html pub fn force_take() -> Result<sync::MutexGuard<'static, Manager>, Error> { - match take() { - Ok(guard) => Ok(guard), - Err(err) => match err { - Error::PoisonError(err) => Ok(err.into_inner()), - err => Err(err), - }, - } + MANAGER.try_lock().or_else(|err| match err { + sync::TryLockError::Poisoned(err) => Ok(err.into_inner()), + sync::TryLockError::WouldBlock => Err(Error::ConcurrentAccessError), + }) } /// List all connected Nitrokey devices. @@ -3,8 +3,6 @@ use std::ffi::CString; -use nitrokey_sys; - use crate::error::Error; use crate::util::{get_command_result, get_cstring, result_from_string}; @@ -349,8 +347,8 @@ pub struct OtpSlotData { pub secret: String, /// The OTP generation mode. pub mode: OtpMode, - /// If true, press the enter key after sending an OTP code using double-pressed - /// numlock, capslock or scrolllock. + /// If true, press the enter key after sending an OTP code using double-pressed Num Lock, Caps + /// Lock or Scroll Lock. pub use_enter: bool, /// Set the token ID, see [OATH Token Identifier Specification][tokspec], section “Class A”. /// @@ -387,8 +385,8 @@ impl OtpSlotData { } } - /// Enables pressing the enter key after sending an OTP code using double-pressed numlock, - /// capslock or scrollock. + /// Enables pressing the enter key after sending an OTP code using double-pressed Num Lock, + /// Caps Lock or Scroll Lock. pub fn use_enter(mut self) -> OtpSlotData { self.use_enter = true; self @@ -1,9 +1,6 @@ // Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org> // SPDX-License-Identifier: MIT -use libc; -use nitrokey_sys; - use crate::device::{Device, DeviceWrapper, Pro, Storage}; use crate::error::{CommandError, Error}; use crate::util::{get_command_result, get_cstring, get_last_error, result_from_string}; diff --git a/src/util.rs b/src/util.rs index b17b071..26942cf 100644 --- a/src/util.rs +++ b/src/util.rs @@ -99,7 +99,7 @@ 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).or_else(|_| Err(LibraryError::InvalidString.into())) + CString::new(s).map_err(|_| LibraryError::InvalidString.into()) } impl Into<i32> for LogLevel { diff --git a/tests/device.rs b/tests/device.rs index 1669d9d..12206e2 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -42,17 +42,12 @@ fn list_devices(_device: DeviceWrapper) { let devices = unwrap_ok!(nitrokey::list_devices()); for device in devices { assert!(!device.path.is_empty()); - if let Some(model) = device.model { - match model { - nitrokey::Model::Pro => { - assert!(device.serial_number.is_some()); - let serial_number = device.serial_number.unwrap(); - assert!(serial_number != SerialNumber::empty()); - } - nitrokey::Model::Storage => { - assert_eq!(None, device.serial_number); - } - } + if Some(nitrokey::Model::Storage) == device.model { + assert_eq!(None, device.serial_number); + } else { + assert!(device.serial_number.is_some()); + let serial_number = device.serial_number.unwrap(); + assert!(serial_number != SerialNumber::empty()); } } } @@ -131,17 +126,14 @@ fn connect_path(device: DeviceWrapper) { for device in devices { let connected_device = unwrap_ok!(manager.connect_path(device.path)); assert_eq!(device.model, Some(connected_device.get_model())); - match device.model.unwrap() { - nitrokey::Model::Pro => { - assert!(device.serial_number.is_some()); - assert_ok!( - device.serial_number.unwrap(), - connected_device.get_serial_number() - ); - } - nitrokey::Model::Storage => { - assert_eq!(None, device.serial_number); - } + if nitrokey::Model::Storage == device.model.unwrap() { + assert_eq!(None, device.serial_number); + } else { + assert!(device.serial_number.is_some()); + assert_ok!( + device.serial_number.unwrap(), + connected_device.get_serial_number() + ); } } } |