From d18cb04ff4d201fe4532cedd22b9753e08385a7f Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 16 Jan 2019 23:08:56 +0000 Subject: Introduce the FirmwareVersion struct The FirmwareVersion struct stores the major and minor firmware version of a Nitrokey device. We refactor the StorageProductionInfo and StorageStatus structs to use this new struct. --- src/device.rs | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/device.rs b/src/device.rs index 9813c50..d794e1b 100644 --- a/src/device.rs +++ b/src/device.rs @@ -225,13 +225,26 @@ pub struct SdCardData { pub manufacturer: u8, } -#[derive(Debug)] -/// Production information for a Storage device. -pub struct StorageProductionInfo { +/// A firmware version for a Nitrokey device. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct FirmwareVersion { /// The major firmware version, e. g. 0 in v0.40. - pub firmware_version_major: u8, + pub major: u8, /// The minor firmware version, e. g. 40 in v0.40. - pub firmware_version_minor: u8, + pub minor: u8, +} + +impl fmt::Display for FirmwareVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "v{}.{}", self.major, self.minor) + } +} + +/// Production information for a Storage device. +#[derive(Debug)] +pub struct StorageProductionInfo { + /// The firmware version. + pub firmware_version: FirmwareVersion, /// The internal firmware version. pub firmware_version_internal: u8, /// The serial number of the CPU. @@ -249,10 +262,8 @@ pub struct StorageStatus { pub encrypted_volume: VolumeStatus, /// The status of the hidden volume. pub hidden_volume: VolumeStatus, - /// The major firmware version, e. g. 0 in v0.40. - pub firmware_version_major: u8, - /// The minor firmware version, e. g. 40 in v0.40. - pub firmware_version_minor: u8, + /// The firmware version. + pub firmware_version: FirmwareVersion, /// Indicates whether the firmware is locked. pub firmware_locked: bool, /// The serial number of the SD card in the Storage stick. @@ -1321,8 +1332,10 @@ impl GenerateOtp for Storage {} impl From for StorageProductionInfo { fn from(data: nitrokey_sys::NK_storage_ProductionTest) -> Self { Self { - firmware_version_major: data.FirmwareVersion_au8[0], - firmware_version_minor: data.FirmwareVersion_au8[1], + 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 { @@ -1352,8 +1365,10 @@ impl From for StorageStatus { read_only: status.hidden_volume_read_only, active: status.hidden_volume_active, }, - firmware_version_major: status.firmware_version_major, - firmware_version_minor: status.firmware_version_minor, + 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, -- cgit v1.2.3 From da2ac74281c4adf4dc10b55ec267d84a8a1502dc Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 16 Jan 2019 23:19:53 +0000 Subject: Implement Display for Version --- CHANGELOG.md | 1 + src/lib.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 901f4e8..fad115b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased - Remove the `test-pro` and `test-storage` features. +- Implement `Display` for `Version`. # v0.3.3 (2019-01-16) - Add the `get_production_info` and `clear_new_sd_card_warning` methods to the diff --git a/src/lib.rs b/src/lib.rs index 02a622b..807a9ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,8 @@ mod otp; mod pws; mod util; +use std::fmt; + use nitrokey_sys; pub use crate::auth::{Admin, Authenticate, User}; @@ -125,6 +127,16 @@ pub struct Version { pub minor: u32, } +impl fmt::Display for Version { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.git.is_empty() { + write!(f, "v{}.{}", self.major, self.minor) + } else { + f.write_str(&self.git) + } + } +} + /// Enables or disables debug output. Calling this method with `true` is equivalent to setting the /// log level to `Debug`; calling it with `false` is equivalent to the log level `Error` (see /// [`set_log_level`][]). -- cgit v1.2.3 From d974ea42b567ad752c53ec0b2b97246d17380632 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sat, 19 Jan 2019 15:18:09 +0000 Subject: Introduce DEFAULT_ADMIN_PIN and DEFAULT_USER_PIN constants The constants can be used for tests or after a factory reset. --- CHANGELOG.md | 1 + src/lib.rs | 5 +++++ 2 files changed, 6 insertions(+) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index fad115b..6d8a698 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - Remove the `test-pro` and `test-storage` features. - Implement `Display` for `Version`. +- Introduce `DEFAULT_ADMIN_PIN` and `DEFAULT_USER_PIN` constants. # v0.3.3 (2019-01-16) - Add the `get_production_info` and `clear_new_sd_card_warning` methods to the diff --git a/src/lib.rs b/src/lib.rs index 807a9ca..93a9894 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,6 +107,11 @@ pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData}; pub use crate::pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT}; pub use crate::util::{CommandError, LogLevel}; +/// The default admin PIN for all Nitrokey devices. +pub const DEFAULT_ADMIN_PIN: &'static str = "12345678"; +/// The default user PIN for all Nitrokey devices. +pub const DEFAULT_USER_PIN: &'static str = "123456"; + /// A version of the libnitrokey library. /// /// Use the [`get_library_version`](fn.get_library_version.html) function to query the library -- cgit v1.2.3 From d0f63513bb935d3d931c86a1ab7b68d6ed44bf27 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 17 Jan 2019 01:53:08 +0000 Subject: Move util::CommandError to the new error module This prepares the refactoring of util::CommandError into multiple enums. --- src/auth.rs | 5 ++- src/config.rs | 2 +- src/device.rs | 5 ++- src/error.rs | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 4 ++- src/otp.rs | 3 +- src/pws.rs | 5 ++- src/util.rs | 113 +-------------------------------------------------------- 8 files changed, 127 insertions(+), 124 deletions(-) create mode 100644 src/error.rs (limited to 'src') diff --git a/src/auth.rs b/src/auth.rs index 2d61d4b..e805e54 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -6,10 +6,9 @@ use nitrokey_sys; use crate::config::{Config, RawConfig}; use crate::device::{Device, DeviceWrapper, Pro, Storage}; +use crate::error::CommandError; use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData, RawOtpSlotData}; -use crate::util::{ - generate_password, get_command_result, get_cstring, result_from_string, CommandError, -}; +use crate::util::{generate_password, get_command_result, get_cstring, result_from_string}; static TEMPORARY_PASSWORD_LENGTH: usize = 25; diff --git a/src/config.rs b/src/config.rs index 2ce6f77..277dc5e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -use crate::util::CommandError; +use crate::error::CommandError; /// The configuration for a Nitrokey. #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/src/device.rs b/src/device.rs index d794e1b..603a986 100644 --- a/src/device.rs +++ b/src/device.rs @@ -5,11 +5,10 @@ use nitrokey_sys; use crate::auth::Authenticate; use crate::config::{Config, RawConfig}; +use crate::error::CommandError; use crate::otp::GenerateOtp; use crate::pws::GetPasswordSafe; -use crate::util::{ - get_command_result, get_cstring, get_last_error, result_from_string, CommandError, -}; +use crate::util::{get_command_result, get_cstring, get_last_error, result_from_string}; /// Available Nitrokey models. #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..6aeeef8 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,114 @@ +use std::borrow; +use std::fmt; +use std::os::raw; + +/// Error types returned by Nitrokey device or by the library. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum CommandError { + /// A packet with a wrong checksum has been sent or received. + WrongCrc, + /// A command tried to access an OTP slot that does not exist. + WrongSlot, + /// A command tried to generate an OTP on a slot that is not configured. + SlotNotProgrammed, + /// The provided password is wrong. + WrongPassword, + /// You are not authorized for this command or provided a wrong temporary + /// password. + NotAuthorized, + /// An error occurred when getting or setting the time. + Timestamp, + /// You did not provide a name for the OTP slot. + NoName, + /// This command is not supported by this device. + NotSupported, + /// This command is unknown. + UnknownCommand, + /// AES decryption failed. + AesDecryptionFailed, + /// An unknown error occurred. + Unknown(i64), + /// An unspecified error occurred. + Undefined, + /// You passed a string containing a null byte. + InvalidString, + /// A supplied string exceeded a length limit. + StringTooLong, + /// You passed an invalid slot. + InvalidSlot, + /// The supplied string was not in hexadecimal format. + InvalidHexString, + /// The target buffer was smaller than the source. + TargetBufferTooSmall, + /// An error occurred during random number generation. + RngError, +} + +impl CommandError { + fn as_str(&self) -> borrow::Cow<'static, str> { + match *self { + CommandError::WrongCrc => { + "A packet with a wrong checksum has been sent or received".into() + } + CommandError::WrongSlot => "The given OTP slot does not exist".into(), + CommandError::SlotNotProgrammed => "The given OTP slot is not programmed".into(), + CommandError::WrongPassword => "The given password is wrong".into(), + CommandError::NotAuthorized => { + "You are not authorized for this command or provided a wrong temporary \ + password" + .into() + } + CommandError::Timestamp => "An error occurred when getting or setting the time".into(), + CommandError::NoName => "You did not provide a name for the OTP slot".into(), + CommandError::NotSupported => "This command is not supported by this device".into(), + CommandError::UnknownCommand => "This command is unknown".into(), + CommandError::AesDecryptionFailed => "AES decryption failed".into(), + CommandError::Unknown(x) => { + borrow::Cow::from(format!("An unknown error occurred ({})", x)) + } + CommandError::Undefined => "An unspecified error occurred".into(), + CommandError::InvalidString => "You passed a string containing a null byte".into(), + CommandError::StringTooLong => "The supplied string is too long".into(), + CommandError::InvalidSlot => "The given slot is invalid".into(), + CommandError::InvalidHexString => { + "The supplied string is not in hexadecimal format".into() + } + CommandError::TargetBufferTooSmall => "The target buffer is too small".into(), + CommandError::RngError => "An error occurred during random number generation".into(), + } + } +} + +impl fmt::Display for CommandError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +impl From for CommandError { + fn from(value: raw::c_int) -> Self { + match value { + 1 => CommandError::WrongCrc, + 2 => CommandError::WrongSlot, + 3 => CommandError::SlotNotProgrammed, + 4 => CommandError::WrongPassword, + 5 => CommandError::NotAuthorized, + 6 => CommandError::Timestamp, + 7 => CommandError::NoName, + 8 => CommandError::NotSupported, + 9 => CommandError::UnknownCommand, + 10 => CommandError::AesDecryptionFailed, + 200 => CommandError::StringTooLong, + 201 => CommandError::InvalidSlot, + 202 => CommandError::InvalidHexString, + 203 => CommandError::TargetBufferTooSmall, + x => CommandError::Unknown(x.into()), + } + } +} + +impl From for CommandError { + fn from(_error: rand_core::Error) -> Self { + CommandError::RngError + } +} diff --git a/src/lib.rs b/src/lib.rs index 93a9894..cec5db7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,6 +89,7 @@ mod auth; mod config; mod device; +mod error; mod otp; mod pws; mod util; @@ -103,9 +104,10 @@ pub use crate::device::{ connect, connect_model, Device, DeviceWrapper, Model, Pro, SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus, }; +pub use crate::error::CommandError; pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData}; pub use crate::pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT}; -pub use crate::util::{CommandError, LogLevel}; +pub use crate::util::LogLevel; /// The default admin PIN for all Nitrokey devices. pub const DEFAULT_ADMIN_PIN: &'static str = "12345678"; diff --git a/src/otp.rs b/src/otp.rs index 901bef9..784149a 100644 --- a/src/otp.rs +++ b/src/otp.rs @@ -2,7 +2,8 @@ use std::ffi::CString; use nitrokey_sys; -use crate::util::{get_command_result, get_cstring, result_from_string, CommandError}; +use crate::error::CommandError; +use crate::util::{get_command_result, get_cstring, result_from_string}; /// Modes for one-time password generation. #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/src/pws.rs b/src/pws.rs index 28f0681..615e47c 100644 --- a/src/pws.rs +++ b/src/pws.rs @@ -2,9 +2,8 @@ use libc; use nitrokey_sys; use crate::device::{Device, DeviceWrapper, Pro, Storage}; -use crate::util::{ - get_command_result, get_cstring, get_last_error, result_from_string, CommandError, -}; +use crate::error::CommandError; +use crate::util::{get_command_result, get_cstring, get_last_error, result_from_string}; /// The number of slots in a [`PasswordSafe`][]. /// diff --git a/src/util.rs b/src/util.rs index 567c478..88a381c 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,53 +1,11 @@ -use std::borrow; use std::ffi::{CStr, CString}; -use std::fmt; use std::os::raw::{c_char, c_int}; use libc::{c_void, free}; use rand_core::RngCore; use rand_os::OsRng; -/// Error types returned by Nitrokey device or by the library. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum CommandError { - /// A packet with a wrong checksum has been sent or received. - WrongCrc, - /// A command tried to access an OTP slot that does not exist. - WrongSlot, - /// A command tried to generate an OTP on a slot that is not configured. - SlotNotProgrammed, - /// The provided password is wrong. - WrongPassword, - /// You are not authorized for this command or provided a wrong temporary - /// password. - NotAuthorized, - /// An error occurred when getting or setting the time. - Timestamp, - /// You did not provide a name for the OTP slot. - NoName, - /// This command is not supported by this device. - NotSupported, - /// This command is unknown. - UnknownCommand, - /// AES decryption failed. - AesDecryptionFailed, - /// An unknown error occurred. - Unknown(i64), - /// An unspecified error occurred. - Undefined, - /// You passed a string containing a null byte. - InvalidString, - /// A supplied string exceeded a length limit. - StringTooLong, - /// You passed an invalid slot. - InvalidSlot, - /// The supplied string was not in hexadecimal format. - InvalidHexString, - /// The target buffer was smaller than the source. - TargetBufferTooSmall, - /// An error occurred during random number generation. - RngError, -} +use crate::error::CommandError; /// Log level for libnitrokey. /// @@ -123,75 +81,6 @@ pub fn get_cstring>>(s: T) -> Result { CString::new(s).or(Err(CommandError::InvalidString)) } -impl CommandError { - fn as_str(&self) -> borrow::Cow<'static, str> { - match *self { - CommandError::WrongCrc => { - "A packet with a wrong checksum has been sent or received".into() - } - CommandError::WrongSlot => "The given OTP slot does not exist".into(), - CommandError::SlotNotProgrammed => "The given OTP slot is not programmed".into(), - CommandError::WrongPassword => "The given password is wrong".into(), - CommandError::NotAuthorized => { - "You are not authorized for this command or provided a wrong temporary \ - password" - .into() - } - CommandError::Timestamp => "An error occurred when getting or setting the time".into(), - CommandError::NoName => "You did not provide a name for the OTP slot".into(), - CommandError::NotSupported => "This command is not supported by this device".into(), - CommandError::UnknownCommand => "This command is unknown".into(), - CommandError::AesDecryptionFailed => "AES decryption failed".into(), - CommandError::Unknown(x) => { - borrow::Cow::from(format!("An unknown error occurred ({})", x)) - } - CommandError::Undefined => "An unspecified error occurred".into(), - CommandError::InvalidString => "You passed a string containing a null byte".into(), - CommandError::StringTooLong => "The supplied string is too long".into(), - CommandError::InvalidSlot => "The given slot is invalid".into(), - CommandError::InvalidHexString => { - "The supplied string is not in hexadecimal format".into() - } - CommandError::TargetBufferTooSmall => "The target buffer is too small".into(), - CommandError::RngError => "An error occurred during random number generation".into(), - } - } -} - -impl fmt::Display for CommandError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.as_str()) - } -} - -impl From for CommandError { - fn from(value: c_int) -> Self { - match value { - 1 => CommandError::WrongCrc, - 2 => CommandError::WrongSlot, - 3 => CommandError::SlotNotProgrammed, - 4 => CommandError::WrongPassword, - 5 => CommandError::NotAuthorized, - 6 => CommandError::Timestamp, - 7 => CommandError::NoName, - 8 => CommandError::NotSupported, - 9 => CommandError::UnknownCommand, - 10 => CommandError::AesDecryptionFailed, - 200 => CommandError::StringTooLong, - 201 => CommandError::InvalidSlot, - 202 => CommandError::InvalidHexString, - 203 => CommandError::TargetBufferTooSmall, - x => CommandError::Unknown(x.into()), - } - } -} - -impl From for CommandError { - fn from(_error: rand_core::Error) -> Self { - CommandError::RngError - } -} - impl Into for LogLevel { fn into(self) -> i32 { match self { -- cgit v1.2.3 From 591c55ff294ee12812e4be1b90f03a093f83a4f5 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 17 Jan 2019 01:58:42 +0000 Subject: Implement std::error::Error for error::CommandError --- CHANGELOG.md | 1 + src/error.rs | 3 +++ 2 files changed, 4 insertions(+) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c5ebf3..d2982a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Remove the `test-pro` and `test-storage` features. - Implement `Display` for `Version`. - Introduce `DEFAULT_ADMIN_PIN` and `DEFAULT_USER_PIN` constants. +- Implement `std::error::Error` for `CommandError`. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/error.rs b/src/error.rs index 6aeeef8..dcaa4d2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,5 @@ use std::borrow; +use std::error; use std::fmt; use std::os::raw; @@ -79,6 +80,8 @@ impl CommandError { } } +impl error::Error for CommandError {} + impl fmt::Display for CommandError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_str()) -- cgit v1.2.3 From db198936be1a80f1735731d9e95eb6f4c48a5329 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 17 Jan 2019 02:05:52 +0000 Subject: Add the Error enum and the Result typedef The Error enum is a wrapper for the possible error types (currently only CommandError). Result is defined as Result. --- CHANGELOG.md | 4 +++- src/error.rs | 35 ++++++++++++++++++++++++++++++++++- src/lib.rs | 2 +- 3 files changed, 38 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index d2982a9..c69fd25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ - Remove the `test-pro` and `test-storage` features. - Implement `Display` for `Version`. - Introduce `DEFAULT_ADMIN_PIN` and `DEFAULT_USER_PIN` constants. -- Implement `std::error::Error` for `CommandError`. +- Refactor the error handling code: + - Implement `std::error::Error` for `CommandError`. + - Add the `Error` enum and the `Result` typedef. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/error.rs b/src/error.rs index dcaa4d2..89c4c82 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,8 +2,41 @@ use std::borrow; use std::error; use std::fmt; use std::os::raw; +use std::result; -/// Error types returned by Nitrokey device or by the library. +/// An error returned by the nitrokey crate. +#[derive(Debug)] +pub enum Error { + /// An error reported by the Nitrokey device in the response packet. + CommandError(CommandError), +} + +impl From for Error { + fn from(err: CommandError) -> Self { + Error::CommandError(err) + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + Error::CommandError(ref err) => Some(err), + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Error::CommandError(ref err) => write!(f, "Command error: {}", err), + } + } +} + +/// A result returned by the nitrokey crate. +pub type Result = result::Result; + +/// An error reported by the Nitrokey device in the response packet. #[derive(Clone, Copy, Debug, PartialEq)] pub enum CommandError { /// A packet with a wrong checksum has been sent or received. diff --git a/src/lib.rs b/src/lib.rs index cec5db7..df11cc3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,7 +104,7 @@ pub use crate::device::{ connect, connect_model, Device, DeviceWrapper, Model, Pro, SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus, }; -pub use crate::error::CommandError; +pub use crate::error::{CommandError, Error, Result}; pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData}; pub use crate::pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT}; pub use crate::util::LogLevel; -- cgit v1.2.3 From 94390aadc8a3997d379bf5e4c0bc00c2a9669a34 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 20 Jan 2019 20:58:18 +0000 Subject: Return Error instead of CommandError This patch changes all public functions to return the Error enum instead of the CommandError enum. This breaks the tests which will be fixed with the next patch. This patch also adds a placeholder variant Error::CommandError and a placeholder enum CommandError to make the transition to a new nitrokey-test version easier. --- CHANGELOG.md | 1 + src/auth.rs | 54 ++++++++--------- src/config.rs | 8 +-- src/device.rs | 182 +++++++++++++++++++++++++++++----------------------------- src/error.rs | 11 ++++ src/lib.rs | 14 ++--- src/otp.rs | 62 ++++++++++---------- src/pws.rs | 62 ++++++++++---------- src/util.rs | 24 ++++---- 9 files changed, 215 insertions(+), 203 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index c69fd25..bbb3202 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Refactor the error handling code: - Implement `std::error::Error` for `CommandError`. - Add the `Error` enum and the `Result` typedef. + - Return `Error` instead of `CommandError` in all public functions. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/auth.rs b/src/auth.rs index e805e54..509d3aa 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -6,7 +6,7 @@ use nitrokey_sys; use crate::config::{Config, RawConfig}; use crate::device::{Device, DeviceWrapper, Pro, Storage}; -use crate::error::CommandError; +use crate::error::{CommandError, Error}; use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData, RawOtpSlotData}; use crate::util::{generate_password, get_command_result, get_cstring, result_from_string}; @@ -33,12 +33,12 @@ pub trait Authenticate { /// /// ```no_run /// use nitrokey::{Authenticate, DeviceWrapper, User}; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// /// fn perform_user_task(device: &User) {} /// fn perform_other_task(device: &DeviceWrapper) {} /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let device = match device.authenticate_user("123456") { /// Ok(user) => { @@ -58,7 +58,7 @@ pub trait Authenticate { /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString /// [`RngError`]: enum.CommandError.html#variant.RngError /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn authenticate_user(self, password: &str) -> Result, (Self, CommandError)> + fn authenticate_user(self, password: &str) -> Result, (Self, Error)> where Self: Device + Sized; @@ -79,12 +79,12 @@ pub trait Authenticate { /// /// ```no_run /// use nitrokey::{Authenticate, Admin, DeviceWrapper}; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// /// fn perform_admin_task(device: &Admin) {} /// fn perform_other_task(device: &DeviceWrapper) {} /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let device = match device.authenticate_admin("123456") { /// Ok(admin) => { @@ -104,7 +104,7 @@ pub trait Authenticate { /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString /// [`RngError`]: enum.CommandError.html#variant.RngError /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn authenticate_admin(self, password: &str) -> Result, (Self, CommandError)> + fn authenticate_admin(self, password: &str) -> Result, (Self, Error)> where Self: Device + Sized; } @@ -143,7 +143,7 @@ pub struct Admin { temp_password: Vec, } -fn authenticate(device: D, password: &str, callback: T) -> Result +fn authenticate(device: D, password: &str, callback: T) -> Result where D: Device, A: AuthenticatedDevice, @@ -161,7 +161,7 @@ where let temp_password_ptr = temp_password.as_ptr() as *const c_char; return match callback(password_ptr, temp_password_ptr) { 0 => Ok(A::new(device, temp_password)), - rv => Err((device, CommandError::from(rv))), + rv => Err((device, CommandError::from(rv).into())), }; } @@ -169,7 +169,7 @@ fn authenticate_user_wrapper( device: T, constructor: C, password: &str, -) -> Result, (DeviceWrapper, CommandError)> +) -> Result, (DeviceWrapper, Error)> where T: Device, C: Fn(T) -> DeviceWrapper, @@ -185,7 +185,7 @@ fn authenticate_admin_wrapper( device: T, constructor: C, password: &str, -) -> Result, (DeviceWrapper, CommandError)> +) -> Result, (DeviceWrapper, Error)> where T: Device, C: Fn(T) -> DeviceWrapper, @@ -215,14 +215,14 @@ impl Deref for User { } impl GenerateOtp for User { - fn get_hotp_code(&self, slot: u8) -> Result { + fn get_hotp_code(&self, slot: u8) -> Result { unsafe { let temp_password_ptr = self.temp_password.as_ptr() as *const c_char; return result_from_string(nitrokey_sys::NK_get_hotp_code_PIN(slot, temp_password_ptr)); } } - fn get_totp_code(&self, slot: u8) -> Result { + fn get_totp_code(&self, slot: u8) -> Result { unsafe { let temp_password_ptr = self.temp_password.as_ptr() as *const c_char; return result_from_string(nitrokey_sys::NK_get_totp_code_PIN( @@ -271,9 +271,9 @@ impl Admin { /// /// ```no_run /// use nitrokey::{Authenticate, Config}; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let config = Config::new(None, None, None, false); /// match device.authenticate_admin("12345678") { @@ -288,7 +288,7 @@ impl Admin { /// ``` /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot - pub fn write_config(&self, config: Config) -> Result<(), CommandError> { + pub fn write_config(&self, config: Config) -> Result<(), Error> { let raw_config = RawConfig::try_from(config)?; unsafe { get_command_result(nitrokey_sys::NK_write_config( @@ -302,7 +302,7 @@ impl Admin { } } - fn write_otp_slot(&self, data: OtpSlotData, callback: C) -> Result<(), CommandError> + fn write_otp_slot(&self, data: OtpSlotData, callback: C) -> Result<(), Error> where C: Fn(RawOtpSlotData, *const c_char) -> c_int, { @@ -313,7 +313,7 @@ impl Admin { } impl ConfigureOtp for Admin { - fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> Result<(), CommandError> { + fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> Result<(), Error> { self.write_otp_slot(data, |raw_data: RawOtpSlotData, temp_password_ptr| unsafe { nitrokey_sys::NK_write_hotp_slot( raw_data.number, @@ -329,7 +329,7 @@ impl ConfigureOtp for Admin { }) } - fn write_totp_slot(&self, data: OtpSlotData, time_window: u16) -> Result<(), CommandError> { + fn write_totp_slot(&self, data: OtpSlotData, time_window: u16) -> Result<(), Error> { self.write_otp_slot(data, |raw_data: RawOtpSlotData, temp_password_ptr| unsafe { nitrokey_sys::NK_write_totp_slot( raw_data.number, @@ -345,12 +345,12 @@ impl ConfigureOtp for Admin { }) } - fn erase_hotp_slot(&self, slot: u8) -> Result<(), CommandError> { + fn erase_hotp_slot(&self, slot: u8) -> Result<(), Error> { let temp_password_ptr = self.temp_password.as_ptr() as *const c_char; unsafe { get_command_result(nitrokey_sys::NK_erase_hotp_slot(slot, temp_password_ptr)) } } - fn erase_totp_slot(&self, slot: u8) -> Result<(), CommandError> { + fn erase_totp_slot(&self, slot: u8) -> Result<(), Error> { let temp_password_ptr = self.temp_password.as_ptr() as *const c_char; unsafe { get_command_result(nitrokey_sys::NK_erase_totp_slot(slot, temp_password_ptr)) } } @@ -366,7 +366,7 @@ impl AuthenticatedDevice for Admin { } impl Authenticate for DeviceWrapper { - fn authenticate_user(self, password: &str) -> Result, (Self, CommandError)> { + fn authenticate_user(self, password: &str) -> Result, (Self, Error)> { match self { DeviceWrapper::Storage(storage) => { authenticate_user_wrapper(storage, DeviceWrapper::Storage, password) @@ -375,7 +375,7 @@ impl Authenticate for DeviceWrapper { } } - fn authenticate_admin(self, password: &str) -> Result, (Self, CommandError)> { + fn authenticate_admin(self, password: &str) -> Result, (Self, Error)> { match self { DeviceWrapper::Storage(storage) => { authenticate_admin_wrapper(storage, DeviceWrapper::Storage, password) @@ -388,13 +388,13 @@ impl Authenticate for DeviceWrapper { } impl Authenticate for Pro { - fn authenticate_user(self, password: &str) -> Result, (Self, CommandError)> { + fn authenticate_user(self, password: &str) -> Result, (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, (Self, CommandError)> { + fn authenticate_admin(self, password: &str) -> Result, (Self, Error)> { authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr) }) @@ -402,13 +402,13 @@ impl Authenticate for Pro { } impl Authenticate for Storage { - fn authenticate_user(self, password: &str) -> Result, (Self, CommandError)> { + fn authenticate_user(self, password: &str) -> Result, (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, (Self, CommandError)> { + fn authenticate_admin(self, password: &str) -> Result, (Self, Error)> { authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr) }) diff --git a/src/config.rs b/src/config.rs index 277dc5e..741d67e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -use crate::error::CommandError; +use crate::error::{CommandError, Error}; /// The configuration for a Nitrokey. #[derive(Clone, Copy, Debug, PartialEq)] @@ -35,13 +35,13 @@ fn config_otp_slot_to_option(value: u8) -> Option { None } -fn option_to_config_otp_slot(value: Option) -> Result { +fn option_to_config_otp_slot(value: Option) -> Result { match value { Some(value) => { if value < 3 { Ok(value) } else { - Err(CommandError::InvalidSlot) + Err(CommandError::InvalidSlot.into()) } } None => Ok(255), @@ -66,7 +66,7 @@ impl Config { } impl RawConfig { - pub fn try_from(config: Config) -> Result { + pub fn try_from(config: Config) -> Result { Ok(RawConfig { numlock: option_to_config_otp_slot(config.numlock)?, capslock: option_to_config_otp_slot(config.capslock)?, diff --git a/src/device.rs b/src/device.rs index 603a986..ccd0597 100644 --- a/src/device.rs +++ b/src/device.rs @@ -5,7 +5,7 @@ use nitrokey_sys; use crate::auth::Authenticate; use crate::config::{Config, RawConfig}; -use crate::error::CommandError; +use crate::error::{CommandError, Error}; use crate::otp::GenerateOtp; use crate::pws::GetPasswordSafe; use crate::util::{get_command_result, get_cstring, get_last_error, result_from_string}; @@ -63,12 +63,12 @@ impl fmt::Display for VolumeMode { /// /// ```no_run /// use nitrokey::{Authenticate, DeviceWrapper, User}; -/// # use nitrokey::CommandError; +/// # use nitrokey::Error; /// /// fn perform_user_task(device: &User) {} /// fn perform_other_task(device: &DeviceWrapper) {} /// -/// # fn try_main() -> Result<(), CommandError> { +/// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let device = match device.authenticate_user("123456") { /// Ok(user) => { @@ -89,12 +89,12 @@ impl fmt::Display for VolumeMode { /// /// ```no_run /// use nitrokey::{DeviceWrapper, Storage}; -/// # use nitrokey::CommandError; +/// # use nitrokey::Error; /// /// fn perform_common_task(device: &DeviceWrapper) {} /// fn perform_storage_task(device: &Storage) {} /// -/// # fn try_main() -> Result<(), CommandError> { +/// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// perform_common_task(&device); /// match device { @@ -127,12 +127,12 @@ pub enum DeviceWrapper { /// /// ```no_run /// use nitrokey::{Authenticate, User, Pro}; -/// # use nitrokey::CommandError; +/// # use nitrokey::Error; /// /// fn perform_user_task(device: &User) {} /// fn perform_other_task(device: &Pro) {} /// -/// # fn try_main() -> Result<(), CommandError> { +/// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::Pro::connect()?; /// let device = match device.authenticate_user("123456") { /// Ok(user) => { @@ -169,12 +169,12 @@ pub struct Pro {} /// /// ```no_run /// use nitrokey::{Authenticate, User, Storage}; -/// # use nitrokey::CommandError; +/// # use nitrokey::Error; /// /// fn perform_user_task(device: &User) {} /// fn perform_other_task(device: &Storage) {} /// -/// # fn try_main() -> Result<(), CommandError> { +/// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::Storage::connect()?; /// let device = match device.authenticate_user("123456") { /// Ok(user) => { @@ -293,9 +293,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// /// ```no_run /// use nitrokey::Device; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// println!("Connected to a Nitrokey {}", device.get_model()); /// # Ok(()) @@ -309,9 +309,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// /// ```no_run /// use nitrokey::Device; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// match device.get_serial_number() { /// Ok(number) => println!("serial no: {}", number), @@ -320,7 +320,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// # Ok(()) /// # } /// ``` - fn get_serial_number(&self) -> Result { + fn get_serial_number(&self) -> Result { unsafe { result_from_string(nitrokey_sys::NK_device_serial_number()) } } @@ -331,9 +331,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// /// ```no_run /// use nitrokey::Device; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let count = device.get_user_retry_count(); /// println!("{} remaining authentication attempts (user)", count); @@ -351,9 +351,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// /// ```no_run /// use nitrokey::Device; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let count = device.get_admin_retry_count(); /// println!("{} remaining authentication attempts (admin)", count); @@ -370,9 +370,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// /// ```no_run /// use nitrokey::Device; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// println!( /// "Firmware version: {}.{}", @@ -392,9 +392,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// /// ```no_run /// use nitrokey::Device; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// println!( /// "Firmware version: {}.{}", @@ -413,9 +413,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// /// ```no_run /// use nitrokey::Device; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let config = device.get_config()?; /// println!("numlock binding: {:?}", config.numlock); @@ -425,7 +425,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// # Ok(()) /// # } /// ``` - fn get_config(&self) -> Result { + fn get_config(&self) -> Result { unsafe { let config_ptr = nitrokey_sys::NK_read_config(); if config_ptr.is_null() { @@ -449,9 +449,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// /// ```no_run /// use nitrokey::Device; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// match device.change_admin_pin("12345678", "12345679") { /// Ok(()) => println!("Updated admin PIN."), @@ -463,7 +463,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn change_admin_pin(&self, current: &str, new: &str) -> Result<(), CommandError> { + fn change_admin_pin(&self, current: &str, new: &str) -> Result<(), Error> { let current_string = get_cstring(current)?; let new_string = get_cstring(new)?; unsafe { @@ -485,9 +485,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// /// ```no_run /// use nitrokey::Device; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// match device.change_user_pin("123456", "123457") { /// Ok(()) => println!("Updated admin PIN."), @@ -499,7 +499,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn change_user_pin(&self, current: &str, new: &str) -> Result<(), CommandError> { + fn change_user_pin(&self, current: &str, new: &str) -> Result<(), Error> { let current_string = get_cstring(current)?; let new_string = get_cstring(new)?; unsafe { @@ -521,9 +521,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// /// ```no_run /// use nitrokey::Device; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// match device.unlock_user_pin("12345678", "123456") { /// Ok(()) => println!("Unlocked user PIN."), @@ -535,7 +535,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn unlock_user_pin(&self, admin_pin: &str, user_pin: &str) -> Result<(), CommandError> { + fn unlock_user_pin(&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)?; unsafe { @@ -555,9 +555,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// /// ```no_run /// use nitrokey::Device; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// match device.lock() { /// Ok(()) => println!("Locked the Nitrokey device."), @@ -566,7 +566,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// # Ok(()) /// # } /// ``` - fn lock(&self) -> Result<(), CommandError> { + fn lock(&self) -> Result<(), Error> { unsafe { get_command_result(nitrokey_sys::NK_lock_device()) } } @@ -586,9 +586,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// /// ```no_run /// use nitrokey::Device; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// match device.factory_reset("12345678") { /// Ok(()) => println!("Performed a factory reset."), @@ -599,7 +599,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// ``` /// /// [`build_aes_key`]: #method.build_aes_key - fn factory_reset(&self, admin_pin: &str) -> Result<(), CommandError> { + fn factory_reset(&self, admin_pin: &str) -> Result<(), Error> { let admin_pin_string = get_cstring(admin_pin)?; unsafe { get_command_result(nitrokey_sys::NK_factory_reset(admin_pin_string.as_ptr())) } } @@ -620,9 +620,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// /// ```no_run /// use nitrokey::Device; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// match device.build_aes_key("12345678") { /// Ok(()) => println!("New AES keys have been built."), @@ -633,7 +633,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// ``` /// /// [`factory_reset`]: #method.factory_reset - fn build_aes_key(&self, admin_pin: &str) -> Result<(), CommandError> { + fn build_aes_key(&self, admin_pin: &str) -> Result<(), Error> { let admin_pin_string = get_cstring(admin_pin)?; unsafe { get_command_result(nitrokey_sys::NK_build_aes_key(admin_pin_string.as_ptr())) } } @@ -660,14 +660,14 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// ``` /// /// [`Undefined`]: enum.CommandError.html#variant.Undefined -pub fn connect() -> Result { +pub fn connect() -> Result { unsafe { match nitrokey_sys::NK_login_auto() { 1 => match get_connected_device() { Some(wrapper) => Ok(wrapper), - None => Err(CommandError::Undefined), + None => Err(CommandError::Undefined.into()), }, - _ => Err(CommandError::Undefined), + _ => Err(CommandError::Undefined.into()), } } } @@ -693,11 +693,11 @@ pub fn connect() -> Result { /// ``` /// /// [`Undefined`]: enum.CommandError.html#variant.Undefined -pub fn connect_model(model: Model) -> Result { +pub fn connect_model(model: Model) -> Result { if connect_enum(model) { Ok(create_device_wrapper(model)) } else { - Err(CommandError::Undefined) + Err(CommandError::Undefined.into()) } } @@ -740,19 +740,19 @@ impl DeviceWrapper { } impl GenerateOtp for DeviceWrapper { - fn get_hotp_slot_name(&self, slot: u8) -> Result { + fn get_hotp_slot_name(&self, slot: u8) -> Result { self.device().get_hotp_slot_name(slot) } - fn get_totp_slot_name(&self, slot: u8) -> Result { + fn get_totp_slot_name(&self, slot: u8) -> Result { self.device().get_totp_slot_name(slot) } - fn get_hotp_code(&self, slot: u8) -> Result { + fn get_hotp_code(&self, slot: u8) -> Result { self.device().get_hotp_code(slot) } - fn get_totp_code(&self, slot: u8) -> Result { + fn get_totp_code(&self, slot: u8) -> Result { self.device().get_totp_code(slot) } } @@ -787,11 +787,11 @@ impl Pro { /// ``` /// /// [`Undefined`]: enum.CommandError.html#variant.Undefined - pub fn connect() -> Result { + pub fn connect() -> Result { // TODO: maybe Option instead of Result? match connect_enum(Model::Pro) { true => Ok(Pro {}), - false => Err(CommandError::Undefined), + false => Err(CommandError::Undefined.into()), } } } @@ -833,11 +833,11 @@ impl Storage { /// ``` /// /// [`Undefined`]: enum.CommandError.html#variant.Undefined - pub fn connect() -> Result { + pub fn connect() -> Result { // TODO: maybe Option instead of Result? match connect_enum(Model::Storage) { true => Ok(Storage {}), - false => Err(CommandError::Undefined), + false => Err(CommandError::Undefined.into()), } } @@ -855,9 +855,9 @@ impl Storage { /// # Example /// /// ```no_run - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::Storage::connect()?; /// match device.change_update_pin("12345678", "87654321") { /// Ok(()) => println!("Updated update PIN."), @@ -869,7 +869,7 @@ impl Storage { /// /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - pub fn change_update_pin(&self, current: &str, new: &str) -> Result<(), CommandError> { + pub fn change_update_pin(&self, current: &str, new: &str) -> Result<(), Error> { let current_string = get_cstring(current)?; let new_string = get_cstring(new)?; unsafe { @@ -895,9 +895,9 @@ impl Storage { /// # Example /// /// ```no_run - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::Storage::connect()?; /// match device.enable_firmware_update("12345678") { /// Ok(()) => println!("Nitrokey entered update mode."), @@ -909,7 +909,7 @@ impl Storage { /// /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - pub fn enable_firmware_update(&self, update_pin: &str) -> Result<(), CommandError> { + pub fn enable_firmware_update(&self, update_pin: &str) -> Result<(), Error> { let update_pin_string = get_cstring(update_pin)?; unsafe { get_command_result(nitrokey_sys::NK_enable_firmware_update( @@ -931,9 +931,9 @@ impl Storage { /// # Example /// /// ```no_run - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::Storage::connect()?; /// match device.enable_encrypted_volume("123456") { /// Ok(()) => println!("Enabled the encrypted volume."), @@ -945,7 +945,7 @@ impl Storage { /// /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - pub fn enable_encrypted_volume(&self, user_pin: &str) -> Result<(), CommandError> { + pub fn enable_encrypted_volume(&self, user_pin: &str) -> Result<(), Error> { let user_pin = get_cstring(user_pin)?; unsafe { get_command_result(nitrokey_sys::NK_unlock_encrypted_volume(user_pin.as_ptr())) } } @@ -958,11 +958,11 @@ impl Storage { /// # Example /// /// ```no_run - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// /// fn use_volume() {} /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::Storage::connect()?; /// match device.enable_encrypted_volume("123456") { /// Ok(()) => { @@ -980,7 +980,7 @@ impl Storage { /// # Ok(()) /// # } /// ``` - pub fn disable_encrypted_volume(&self) -> Result<(), CommandError> { + pub fn disable_encrypted_volume(&self) -> Result<(), Error> { unsafe { get_command_result(nitrokey_sys::NK_lock_encrypted_volume()) } } @@ -1006,9 +1006,9 @@ impl Storage { /// # Example /// /// ```no_run - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::Storage::connect()?; /// device.enable_encrypted_volume("123445")?; /// match device.enable_hidden_volume("hidden-pw") { @@ -1022,7 +1022,7 @@ impl Storage { /// [`enable_encrypted_volume`]: #method.enable_encrypted_volume /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString - pub fn enable_hidden_volume(&self, volume_password: &str) -> Result<(), CommandError> { + pub fn enable_hidden_volume(&self, volume_password: &str) -> Result<(), Error> { let volume_password = get_cstring(volume_password)?; unsafe { get_command_result(nitrokey_sys::NK_unlock_hidden_volume( @@ -1039,11 +1039,11 @@ impl Storage { /// # Example /// /// ```no_run - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// /// fn use_volume() {} /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::Storage::connect()?; /// device.enable_encrypted_volume("123445")?; /// match device.enable_hidden_volume("hidden-pw") { @@ -1062,7 +1062,7 @@ impl Storage { /// # Ok(()) /// # } /// ``` - pub fn disable_hidden_volume(&self) -> Result<(), CommandError> { + pub fn disable_hidden_volume(&self) -> Result<(), Error> { unsafe { get_command_result(nitrokey_sys::NK_lock_hidden_volume()) } } @@ -1088,9 +1088,9 @@ impl Storage { /// # Example /// /// ```no_run - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::Storage::connect()?; /// device.enable_encrypted_volume("123445")?; /// device.create_hidden_volume(0, 0, 100, "hidden-pw")?; @@ -1106,7 +1106,7 @@ impl Storage { start: u8, end: u8, password: &str, - ) -> Result<(), CommandError> { + ) -> Result<(), Error> { let password = get_cstring(password)?; unsafe { get_command_result(nitrokey_sys::NK_create_hidden_volume( @@ -1132,10 +1132,10 @@ impl Storage { /// # Example /// /// ```no_run - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// use nitrokey::VolumeMode; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::Storage::connect()?; /// match device.set_unencrypted_volume_mode("123456", VolumeMode::ReadWrite) { /// Ok(()) => println!("Set the unencrypted volume to read-write mode."), @@ -1151,7 +1151,7 @@ impl Storage { &self, admin_pin: &str, mode: VolumeMode, - ) -> Result<(), CommandError> { + ) -> Result<(), Error> { let admin_pin = get_cstring(admin_pin)?; let result = match mode { VolumeMode::ReadOnly => unsafe { @@ -1169,11 +1169,11 @@ impl Storage { /// # Example /// /// ```no_run - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// /// fn use_volume() {} /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::Storage::connect()?; /// match device.get_status() { /// Ok(status) => { @@ -1184,7 +1184,7 @@ impl Storage { /// # Ok(()) /// # } /// ``` - pub fn get_status(&self) -> Result { + pub fn get_status(&self) -> Result { let mut raw_status = nitrokey_sys::NK_storage_status { unencrypted_volume_read_only: false, unencrypted_volume_active: false, @@ -1213,11 +1213,11 @@ impl Storage { /// # Example /// /// ```no_run - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// /// fn use_volume() {} /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::Storage::connect()?; /// match device.get_production_info() { /// Ok(data) => { @@ -1229,7 +1229,7 @@ impl Storage { /// # Ok(()) /// # } /// ``` - pub fn get_production_info(&self) -> Result { + pub fn get_production_info(&self) -> Result { let mut raw_data = nitrokey_sys::NK_storage_ProductionTest { FirmwareVersion_au8: [0, 2], FirmwareVersionInternal_u8: 0, @@ -1264,9 +1264,9 @@ impl Storage { /// # Example /// /// ```no_run - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::Storage::connect()?; /// match device.clear_new_sd_card_warning("12345678") { /// Ok(()) => println!("Cleared the new SD card warning."), @@ -1278,7 +1278,7 @@ impl Storage { /// /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - pub fn clear_new_sd_card_warning(&self, admin_pin: &str) -> Result<(), CommandError> { + pub fn clear_new_sd_card_warning(&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()) @@ -1286,7 +1286,7 @@ impl Storage { } /// Blinks the red and green LED alternatively and infinitely until the device is reconnected. - pub fn wink(&self) -> Result<(), CommandError> { + pub fn wink(&self) -> Result<(), Error> { get_command_result(unsafe { nitrokey_sys::NK_wink() }) } @@ -1306,7 +1306,7 @@ impl Storage { /// /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - pub fn export_firmware(&self, admin_pin: &str) -> Result<(), CommandError> { + pub fn export_firmware(&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()) }) } diff --git a/src/error.rs b/src/error.rs index 89c4c82..3f60af2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,6 +9,8 @@ use std::result; pub enum Error { /// An error reported by the Nitrokey device in the response packet. CommandError(CommandError), + /// Placeholder for testing. + CommunicationError(CommunicationError), } impl From for Error { @@ -21,6 +23,7 @@ impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { Error::CommandError(ref err) => Some(err), + Error::CommunicationError(_) => None, } } } @@ -29,6 +32,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Error::CommandError(ref err) => write!(f, "Command error: {}", err), + Error::CommunicationError(_) => write!(f, "Placeholder"), } } } @@ -78,6 +82,13 @@ pub enum CommandError { RngError, } +/// Placeholder for testing. +#[derive(Debug)] +pub enum CommunicationError { + /// Placeholder for testing. + NotConnected, +} + impl CommandError { fn as_str(&self) -> borrow::Cow<'static, str> { match *self { diff --git a/src/lib.rs b/src/lib.rs index df11cc3..8522e83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,9 +25,9 @@ //! //! ```no_run //! use nitrokey::Device; -//! # use nitrokey::CommandError; +//! # use nitrokey::Error; //! -//! # fn try_main() -> Result<(), CommandError> { +//! # fn try_main() -> Result<(), Error> { //! let device = nitrokey::connect()?; //! println!("{}", device.get_serial_number()?); //! # Ok(()) @@ -38,9 +38,9 @@ //! //! ```no_run //! use nitrokey::{Authenticate, ConfigureOtp, OtpMode, OtpSlotData}; -//! # use nitrokey::CommandError; +//! # use nitrokey::Error; //! -//! # fn try_main() -> Result<(), (CommandError)> { +//! # fn try_main() -> Result<(), Error> { //! let device = nitrokey::connect()?; //! let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits); //! match device.authenticate_admin("12345678") { @@ -60,9 +60,9 @@ //! //! ```no_run //! use nitrokey::{Device, GenerateOtp}; -//! # use nitrokey::CommandError; +//! # use nitrokey::Error; //! -//! # fn try_main() -> Result<(), (CommandError)> { +//! # fn try_main() -> Result<(), Error> { //! let device = nitrokey::connect()?; //! match device.get_hotp_code(1) { //! Ok(code) => println!("Generated HOTP code: {}", code), @@ -104,7 +104,7 @@ pub use crate::device::{ connect, connect_model, Device, DeviceWrapper, Model, Pro, SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus, }; -pub use crate::error::{CommandError, Error, Result}; +pub use crate::error::{CommandError, CommunicationError, Error, Result}; pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData}; pub use crate::pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT}; pub use crate::util::LogLevel; diff --git a/src/otp.rs b/src/otp.rs index 784149a..5dfe8b1 100644 --- a/src/otp.rs +++ b/src/otp.rs @@ -2,7 +2,7 @@ use std::ffi::CString; use nitrokey_sys; -use crate::error::CommandError; +use crate::error::Error; use crate::util::{get_command_result, get_cstring, result_from_string}; /// Modes for one-time password generation. @@ -29,9 +29,9 @@ pub trait ConfigureOtp { /// /// ```no_run /// use nitrokey::{Authenticate, ConfigureOtp, OtpMode, OtpSlotData}; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), (CommandError)> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits); /// match device.authenticate_admin("12345678") { @@ -50,7 +50,7 @@ pub trait ConfigureOtp { /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString /// [`NoName`]: enum.CommandError.html#variant.NoName - fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> Result<(), CommandError>; + fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> Result<(), Error>; /// Configure a TOTP slot with the given data and set the TOTP time window to the given value /// (default 30). @@ -65,9 +65,9 @@ pub trait ConfigureOtp { /// /// ```no_run /// use nitrokey::{Authenticate, ConfigureOtp, OtpMode, OtpSlotData}; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), (CommandError)> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::EightDigits); /// match device.authenticate_admin("12345678") { @@ -86,7 +86,7 @@ pub trait ConfigureOtp { /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString /// [`NoName`]: enum.CommandError.html#variant.NoName - fn write_totp_slot(&self, data: OtpSlotData, time_window: u16) -> Result<(), CommandError>; + fn write_totp_slot(&self, data: OtpSlotData, time_window: u16) -> Result<(), Error>; /// Erases an HOTP slot. /// @@ -98,9 +98,9 @@ pub trait ConfigureOtp { /// /// ```no_run /// use nitrokey::{Authenticate, ConfigureOtp}; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), (CommandError)> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// match device.authenticate_admin("12345678") { /// Ok(admin) => { @@ -116,7 +116,7 @@ pub trait ConfigureOtp { /// ``` /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot - fn erase_hotp_slot(&self, slot: u8) -> Result<(), CommandError>; + fn erase_hotp_slot(&self, slot: u8) -> Result<(), Error>; /// Erases a TOTP slot. /// @@ -128,9 +128,9 @@ pub trait ConfigureOtp { /// /// ```no_run /// use nitrokey::{Authenticate, ConfigureOtp}; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), (CommandError)> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// match device.authenticate_admin("12345678") { /// Ok(admin) => { @@ -146,7 +146,7 @@ pub trait ConfigureOtp { /// ``` /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot - fn erase_totp_slot(&self, slot: u8) -> Result<(), CommandError>; + fn erase_totp_slot(&self, slot: u8) -> Result<(), Error>; } /// Provides methods to generate OTP codes and to query OTP slots on a Nitrokey @@ -165,9 +165,9 @@ pub trait GenerateOtp { /// ```no_run /// use std::time; /// use nitrokey::GenerateOtp; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH); /// match time { @@ -184,7 +184,7 @@ pub trait GenerateOtp { /// /// [`get_totp_code`]: #method.get_totp_code /// [`Timestamp`]: enum.CommandError.html#variant.Timestamp - fn set_time(&self, time: u64, force: bool) -> Result<(), CommandError> { + fn set_time(&self, time: u64, force: bool) -> Result<(), Error> { let result = if force { unsafe { nitrokey_sys::NK_totp_set_time(time) } } else { @@ -203,13 +203,13 @@ pub trait GenerateOtp { /// # Example /// /// ```no_run - /// use nitrokey::{CommandError, GenerateOtp}; + /// use nitrokey::{CommandError, Error, GenerateOtp}; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// match device.get_hotp_slot_name(1) { /// Ok(name) => println!("HOTP slot 1: {}", name), - /// Err(CommandError::SlotNotProgrammed) => println!("HOTP slot 1 not programmed"), + /// Err(Error::CommandError(CommandError::SlotNotProgrammed)) => println!("HOTP slot 1 not programmed"), /// Err(err) => println!("Could not get slot name: {}", err), /// }; /// # Ok(()) @@ -218,7 +218,7 @@ pub trait GenerateOtp { /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed - fn get_hotp_slot_name(&self, slot: u8) -> Result { + fn get_hotp_slot_name(&self, slot: u8) -> Result { unsafe { result_from_string(nitrokey_sys::NK_get_hotp_slot_name(slot)) } } @@ -232,13 +232,13 @@ pub trait GenerateOtp { /// # Example /// /// ```no_run - /// use nitrokey::{CommandError, GenerateOtp}; + /// use nitrokey::{CommandError, Error, GenerateOtp}; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// match device.get_totp_slot_name(1) { /// Ok(name) => println!("TOTP slot 1: {}", name), - /// Err(CommandError::SlotNotProgrammed) => println!("TOTP slot 1 not programmed"), + /// Err(Error::CommandError(CommandError::SlotNotProgrammed)) => println!("TOTP slot 1 not programmed"), /// Err(err) => println!("Could not get slot name: {}", err), /// }; /// # Ok(()) @@ -247,7 +247,7 @@ pub trait GenerateOtp { /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed - fn get_totp_slot_name(&self, slot: u8) -> Result { + fn get_totp_slot_name(&self, slot: u8) -> Result { unsafe { result_from_string(nitrokey_sys::NK_get_totp_slot_name(slot)) } } @@ -264,9 +264,9 @@ pub trait GenerateOtp { /// /// ```no_run /// use nitrokey::GenerateOtp; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let code = device.get_hotp_code(1)?; /// println!("Generated HOTP code on slot 1: {}", code); @@ -278,7 +278,7 @@ pub trait GenerateOtp { /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot /// [`NotAuthorized`]: enum.CommandError.html#variant.NotAuthorized /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed - fn get_hotp_code(&self, slot: u8) -> Result { + fn get_hotp_code(&self, slot: u8) -> Result { unsafe { return result_from_string(nitrokey_sys::NK_get_hotp_code(slot)); } @@ -301,9 +301,9 @@ pub trait GenerateOtp { /// ```no_run /// use std::time; /// use nitrokey::GenerateOtp; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH); /// match time { @@ -323,7 +323,7 @@ pub trait GenerateOtp { /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot /// [`NotAuthorized`]: enum.CommandError.html#variant.NotAuthorized /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed - fn get_totp_code(&self, slot: u8) -> Result { + fn get_totp_code(&self, slot: u8) -> Result { unsafe { return result_from_string(nitrokey_sys::NK_get_totp_code(slot, 0, 0, 0)); } @@ -396,7 +396,7 @@ impl OtpSlotData { } impl RawOtpSlotData { - pub fn new(data: OtpSlotData) -> Result { + pub fn new(data: OtpSlotData) -> Result { let name = get_cstring(data.name)?; let secret = get_cstring(data.secret)?; let use_token_id = data.token_id.is_some(); diff --git a/src/pws.rs b/src/pws.rs index 615e47c..e974737 100644 --- a/src/pws.rs +++ b/src/pws.rs @@ -2,7 +2,7 @@ use libc; use nitrokey_sys; use crate::device::{Device, DeviceWrapper, Pro, Storage}; -use crate::error::CommandError; +use crate::error::{CommandError, Error}; use crate::util::{get_command_result, get_cstring, get_last_error, result_from_string}; /// The number of slots in a [`PasswordSafe`][]. @@ -29,9 +29,9 @@ pub const SLOT_COUNT: u8 = 16; /// /// ```no_run /// use nitrokey::{Device, GetPasswordSafe, PasswordSafe}; -/// # use nitrokey::CommandError; +/// # use nitrokey::Error; /// -/// fn use_password_safe(pws: &PasswordSafe) -> Result<(), CommandError> { +/// fn use_password_safe(pws: &PasswordSafe) -> Result<(), Error> { /// let name = pws.get_slot_name(0)?; /// let login = pws.get_slot_login(0)?; /// let password = pws.get_slot_login(0)?; @@ -39,7 +39,7 @@ pub const SLOT_COUNT: u8 = 16; /// Ok(()) /// } /// -/// # fn try_main() -> Result<(), CommandError> { +/// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let pws = device.get_password_safe("123456")?; /// use_password_safe(&pws); @@ -88,11 +88,11 @@ pub trait GetPasswordSafe { /// /// ```no_run /// use nitrokey::{Device, GetPasswordSafe, PasswordSafe}; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// /// fn use_password_safe(pws: &PasswordSafe) {} /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// match device.get_password_safe("123456") { /// Ok(pws) => { @@ -112,13 +112,13 @@ pub trait GetPasswordSafe { /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString /// [`Unknown`]: enum.CommandError.html#variant.Unknown /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn get_password_safe(&self, user_pin: &str) -> Result, CommandError>; + fn get_password_safe(&self, user_pin: &str) -> Result, Error>; } fn get_password_safe<'a>( device: &'a dyn Device, user_pin: &str, -) -> Result, CommandError> { +) -> Result, Error> { let user_pin_string = get_cstring(user_pin)?; let result = unsafe { get_command_result(nitrokey_sys::NK_enable_password_safe( @@ -128,9 +128,9 @@ fn get_password_safe<'a>( result.map(|()| PasswordSafe { _device: device }) } -fn get_pws_result(s: String) -> Result { +fn get_pws_result(s: String) -> Result { if s.is_empty() { - Err(CommandError::SlotNotProgrammed) + Err(CommandError::SlotNotProgrammed.into()) } else { Ok(s) } @@ -145,9 +145,9 @@ impl<'a> PasswordSafe<'a> { /// /// ```no_run /// use nitrokey::{GetPasswordSafe, SLOT_COUNT}; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let pws = device.get_password_safe("123456")?; /// pws.get_slot_status()?.iter().enumerate().for_each(|(slot, programmed)| { @@ -160,7 +160,7 @@ impl<'a> PasswordSafe<'a> { /// # Ok(()) /// # } /// ``` - pub fn get_slot_status(&self) -> Result<[bool; SLOT_COUNT as usize], CommandError> { + pub fn get_slot_status(&self) -> Result<[bool; SLOT_COUNT as usize], Error> { let status_ptr = unsafe { nitrokey_sys::NK_get_password_safe_slot_status() }; if status_ptr.is_null() { return Err(get_last_error()); @@ -190,9 +190,9 @@ impl<'a> PasswordSafe<'a> { /// /// ```no_run /// use nitrokey::GetPasswordSafe; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// match device.get_password_safe("123456") { /// Ok(pws) => { @@ -209,7 +209,7 @@ impl<'a> PasswordSafe<'a> { /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed - pub fn get_slot_name(&self, slot: u8) -> Result { + pub fn get_slot_name(&self, slot: u8) -> Result { unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_name(slot)) } .and_then(get_pws_result) } @@ -227,9 +227,9 @@ impl<'a> PasswordSafe<'a> { /// /// ```no_run /// use nitrokey::GetPasswordSafe; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let pws = device.get_password_safe("123456")?; /// let name = pws.get_slot_name(0)?; @@ -242,7 +242,7 @@ impl<'a> PasswordSafe<'a> { /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed - pub fn get_slot_login(&self, slot: u8) -> Result { + pub fn get_slot_login(&self, slot: u8) -> Result { unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_login(slot)) } .and_then(get_pws_result) } @@ -260,9 +260,9 @@ impl<'a> PasswordSafe<'a> { /// /// ```no_run /// use nitrokey::GetPasswordSafe; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let pws = device.get_password_safe("123456")?; /// let name = pws.get_slot_name(0)?; @@ -275,7 +275,7 @@ impl<'a> PasswordSafe<'a> { /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed - pub fn get_slot_password(&self, slot: u8) -> Result { + pub fn get_slot_password(&self, slot: u8) -> Result { unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_password(slot)) } .and_then(get_pws_result) } @@ -291,9 +291,9 @@ impl<'a> PasswordSafe<'a> { /// /// ```no_run /// use nitrokey::GetPasswordSafe; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let pws = device.get_password_safe("123456")?; /// let name = pws.get_slot_name(0)?; @@ -312,7 +312,7 @@ impl<'a> PasswordSafe<'a> { name: &str, login: &str, password: &str, - ) -> Result<(), CommandError> { + ) -> Result<(), Error> { let name_string = get_cstring(name)?; let login_string = get_cstring(login)?; let password_string = get_cstring(password)?; @@ -337,9 +337,9 @@ impl<'a> PasswordSafe<'a> { /// /// ```no_run /// use nitrokey::GetPasswordSafe; - /// # use nitrokey::CommandError; + /// # use nitrokey::Error; /// - /// # fn try_main() -> Result<(), CommandError> { + /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let pws = device.get_password_safe("123456")?; /// match pws.erase_slot(0) { @@ -351,7 +351,7 @@ impl<'a> PasswordSafe<'a> { /// ``` /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot - pub fn erase_slot(&self, slot: u8) -> Result<(), CommandError> { + pub fn erase_slot(&self, slot: u8) -> Result<(), Error> { unsafe { get_command_result(nitrokey_sys::NK_erase_password_safe_slot(slot)) } } } @@ -364,19 +364,19 @@ impl<'a> Drop for PasswordSafe<'a> { } impl GetPasswordSafe for Pro { - fn get_password_safe(&self, user_pin: &str) -> Result, CommandError> { + fn get_password_safe(&self, user_pin: &str) -> Result, Error> { get_password_safe(self, user_pin) } } impl GetPasswordSafe for Storage { - fn get_password_safe(&self, user_pin: &str) -> Result, CommandError> { + fn get_password_safe(&self, user_pin: &str) -> Result, Error> { get_password_safe(self, user_pin) } } impl GetPasswordSafe for DeviceWrapper { - fn get_password_safe(&self, user_pin: &str) -> Result, CommandError> { + fn get_password_safe(&self, user_pin: &str) -> Result, Error> { get_password_safe(self, user_pin) } } diff --git a/src/util.rs b/src/util.rs index 88a381c..8855275 100644 --- a/src/util.rs +++ b/src/util.rs @@ -5,7 +5,7 @@ use libc::{c_void, free}; use rand_core::RngCore; use rand_os::OsRng; -use crate::error::CommandError; +use crate::error::{CommandError, Error}; /// Log level for libnitrokey. /// @@ -34,9 +34,9 @@ pub fn owned_str_from_ptr(ptr: *const c_char) -> String { } } -pub fn result_from_string(ptr: *const c_char) -> Result { +pub fn result_from_string(ptr: *const c_char) -> Result { if ptr.is_null() { - return Err(CommandError::Undefined); + return Err(CommandError::Undefined.into()); } unsafe { let s = owned_str_from_ptr(ptr); @@ -51,34 +51,34 @@ pub fn result_from_string(ptr: *const c_char) -> Result { } } -pub fn get_command_result(value: c_int) -> Result<(), CommandError> { +pub fn get_command_result(value: c_int) -> Result<(), Error> { match value { 0 => Ok(()), - other => Err(CommandError::from(other)), + other => Err(CommandError::from(other).into()), } } -pub fn get_last_result() -> Result<(), CommandError> { +pub fn get_last_result() -> Result<(), Error> { let value = unsafe { nitrokey_sys::NK_get_last_command_status() } as c_int; get_command_result(value) } -pub fn get_last_error() -> CommandError { +pub fn get_last_error() -> Error { return match get_last_result() { - Ok(()) => CommandError::Undefined, + Ok(()) => CommandError::Undefined.into(), Err(err) => err, }; } -pub fn generate_password(length: usize) -> Result, CommandError> { - let mut rng = OsRng::new()?; +pub fn generate_password(length: usize) -> Result, Error> { + let mut rng = OsRng::new().map_err(CommandError::from)?; let mut data = vec![0u8; length]; rng.fill_bytes(&mut data[..]); Ok(data) } -pub fn get_cstring>>(s: T) -> Result { - CString::new(s).or(Err(CommandError::InvalidString)) +pub fn get_cstring>>(s: T) -> Result { + CString::new(s).or(Err(CommandError::InvalidString.into())) } impl Into for LogLevel { -- cgit v1.2.3 From cafc3a6f8cfb9f82343c1d3fe843c7f8d7ef1136 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 20 Jan 2019 21:05:58 +0000 Subject: Refactor CommandError::RngError into Error::RandError We reserve CommandError for errors returned by the Nitrokey device. Errors during random number generation should have their own type. --- CHANGELOG.md | 1 + src/error.rs | 19 ++++++++++--------- src/util.rs | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index bbb3202..5a78724 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Implement `std::error::Error` for `CommandError`. - Add the `Error` enum and the `Result` typedef. - Return `Error` instead of `CommandError` in all public functions. + - Move the `CommandError::RngError` variant to `Error::RandError`. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/error.rs b/src/error.rs index 3f60af2..dfe1680 100644 --- a/src/error.rs +++ b/src/error.rs @@ -11,6 +11,8 @@ pub enum Error { CommandError(CommandError), /// Placeholder for testing. CommunicationError(CommunicationError), + /// An error that occured during random number generation. + RandError(rand_core::Error), } impl From for Error { @@ -19,11 +21,18 @@ impl From for Error { } } +impl From for Error { + fn from(error: rand_core::Error) -> Self { + Error::RandError(error) + } +} + impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { Error::CommandError(ref err) => Some(err), Error::CommunicationError(_) => None, + Error::RandError(ref err) => Some(err), } } } @@ -33,6 +42,7 @@ impl fmt::Display for Error { match *self { Error::CommandError(ref err) => write!(f, "Command error: {}", err), Error::CommunicationError(_) => write!(f, "Placeholder"), + Error::RandError(ref err) => write!(f, "RNG error: {}", err), } } } @@ -78,8 +88,6 @@ pub enum CommandError { InvalidHexString, /// The target buffer was smaller than the source. TargetBufferTooSmall, - /// An error occurred during random number generation. - RngError, } /// Placeholder for testing. @@ -119,7 +127,6 @@ impl CommandError { "The supplied string is not in hexadecimal format".into() } CommandError::TargetBufferTooSmall => "The target buffer is too small".into(), - CommandError::RngError => "An error occurred during random number generation".into(), } } } @@ -153,9 +160,3 @@ impl From for CommandError { } } } - -impl From for CommandError { - fn from(_error: rand_core::Error) -> Self { - CommandError::RngError - } -} diff --git a/src/util.rs b/src/util.rs index 8855275..06bb854 100644 --- a/src/util.rs +++ b/src/util.rs @@ -71,7 +71,7 @@ pub fn get_last_error() -> Error { } pub fn generate_password(length: usize) -> Result, Error> { - let mut rng = OsRng::new().map_err(CommandError::from)?; + let mut rng = OsRng::new()?; let mut data = vec![0u8; length]; rng.fill_bytes(&mut data[..]); Ok(data) -- cgit v1.2.3 From 944e1fa0d51e547dde2a9368d2b8431b109f63c4 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 17 Jan 2019 04:15:31 +0000 Subject: Move the CommandError::Unknown to Error An error code can not only indiciate a command error, but also a library or device communication error. Therefore, the variant for an unknown error code should be placed in the top-level Error enum instead of the CommandError enum. --- CHANGELOG.md | 3 ++- src/auth.rs | 4 ++-- src/error.rs | 61 +++++++++++++++++++++++++++++++++--------------------------- src/util.rs | 2 +- tests/pws.rs | 2 +- 5 files changed, 40 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a78724..def5273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ - Implement `std::error::Error` for `CommandError`. - Add the `Error` enum and the `Result` typedef. - Return `Error` instead of `CommandError` in all public functions. - - Move the `CommandError::RngError` variant to `Error::RandError`. + - Move the `CommandError::RngError` variant to `Error::RandError` and the + `CommandError::Unknown` variant to `Error::Unknown`. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/auth.rs b/src/auth.rs index 509d3aa..e05f6b3 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -6,7 +6,7 @@ use nitrokey_sys; use crate::config::{Config, RawConfig}; use crate::device::{Device, DeviceWrapper, Pro, Storage}; -use crate::error::{CommandError, Error}; +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}; @@ -161,7 +161,7 @@ where let temp_password_ptr = temp_password.as_ptr() as *const c_char; return match callback(password_ptr, temp_password_ptr) { 0 => Ok(A::new(device, temp_password)), - rv => Err((device, CommandError::from(rv).into())), + rv => Err((device, Error::from(rv))), }; } diff --git a/src/error.rs b/src/error.rs index dfe1680..c5a975e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,6 +13,18 @@ pub enum Error { CommunicationError(CommunicationError), /// An error that occured during random number generation. RandError(rand_core::Error), + /// An unknown error returned by libnitrokey. + Unknown(i64), +} + +impl From for Error { + fn from(code: raw::c_int) -> Self { + if let Some(err) = CommandError::try_from(code) { + Error::CommandError(err) + } else { + Error::Unknown(code.into()) + } + } } impl From for Error { @@ -33,6 +45,7 @@ impl error::Error for Error { Error::CommandError(ref err) => Some(err), Error::CommunicationError(_) => None, Error::RandError(ref err) => Some(err), + Error::Unknown(_) => None, } } } @@ -43,6 +56,7 @@ impl fmt::Display for Error { Error::CommandError(ref err) => write!(f, "Command error: {}", err), Error::CommunicationError(_) => write!(f, "Placeholder"), Error::RandError(ref err) => write!(f, "RNG error: {}", err), + Error::Unknown(ref err) => write!(f, "Unknown error: {}", err), } } } @@ -74,8 +88,6 @@ pub enum CommandError { UnknownCommand, /// AES decryption failed. AesDecryptionFailed, - /// An unknown error occurred. - Unknown(i64), /// An unspecified error occurred. Undefined, /// You passed a string containing a null byte. @@ -98,6 +110,26 @@ pub enum CommunicationError { } impl CommandError { + fn try_from(value: raw::c_int) -> Option { + match value { + 1 => Some(CommandError::WrongCrc), + 2 => Some(CommandError::WrongSlot), + 3 => Some(CommandError::SlotNotProgrammed), + 4 => Some(CommandError::WrongPassword), + 5 => Some(CommandError::NotAuthorized), + 6 => Some(CommandError::Timestamp), + 7 => Some(CommandError::NoName), + 8 => Some(CommandError::NotSupported), + 9 => Some(CommandError::UnknownCommand), + 10 => Some(CommandError::AesDecryptionFailed), + 200 => Some(CommandError::StringTooLong), + 201 => Some(CommandError::InvalidSlot), + 202 => Some(CommandError::InvalidHexString), + 203 => Some(CommandError::TargetBufferTooSmall), + _ => None, + } + } + fn as_str(&self) -> borrow::Cow<'static, str> { match *self { CommandError::WrongCrc => { @@ -116,9 +148,6 @@ impl CommandError { CommandError::NotSupported => "This command is not supported by this device".into(), CommandError::UnknownCommand => "This command is unknown".into(), CommandError::AesDecryptionFailed => "AES decryption failed".into(), - CommandError::Unknown(x) => { - borrow::Cow::from(format!("An unknown error occurred ({})", x)) - } CommandError::Undefined => "An unspecified error occurred".into(), CommandError::InvalidString => "You passed a string containing a null byte".into(), CommandError::StringTooLong => "The supplied string is too long".into(), @@ -138,25 +167,3 @@ impl fmt::Display for CommandError { write!(f, "{}", self.as_str()) } } - -impl From for CommandError { - fn from(value: raw::c_int) -> Self { - match value { - 1 => CommandError::WrongCrc, - 2 => CommandError::WrongSlot, - 3 => CommandError::SlotNotProgrammed, - 4 => CommandError::WrongPassword, - 5 => CommandError::NotAuthorized, - 6 => CommandError::Timestamp, - 7 => CommandError::NoName, - 8 => CommandError::NotSupported, - 9 => CommandError::UnknownCommand, - 10 => CommandError::AesDecryptionFailed, - 200 => CommandError::StringTooLong, - 201 => CommandError::InvalidSlot, - 202 => CommandError::InvalidHexString, - 203 => CommandError::TargetBufferTooSmall, - x => CommandError::Unknown(x.into()), - } - } -} diff --git a/src/util.rs b/src/util.rs index 06bb854..3b9904f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -54,7 +54,7 @@ pub fn result_from_string(ptr: *const c_char) -> Result { pub fn get_command_result(value: c_int) -> Result<(), Error> { match value { 0 => Ok(()), - other => Err(CommandError::from(other).into()), + other => Err(Error::from(other)), } } diff --git a/tests/pws.rs b/tests/pws.rs index f12af48..a4647bd 100644 --- a/tests/pws.rs +++ b/tests/pws.rs @@ -21,7 +21,7 @@ fn get_slot_name_direct(slot: u8) -> Result { let error = unsafe { nitrokey_sys::NK_get_last_command_status() } as c_int; match error { 0 => Ok(s), - other => Err(CommandError::from(other).into()), + other => Err(Error::from(other)), } } false => Ok(s), -- cgit v1.2.3 From 5e258d26b55af6bed7c316b1c7ac12e20946702d Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 17 Jan 2019 12:47:52 +0000 Subject: Refactor library errors into LibraryError enum Previously, library errors were part of the CommandError enum. As command errors and library errors are two different error types, they should be split into two enums. --- CHANGELOG.md | 2 ++ src/auth.rs | 6 ++--- src/config.rs | 4 +-- src/device.rs | 22 ++++++++--------- src/error.rs | 74 +++++++++++++++++++++++++++++++++++++++---------------- src/lib.rs | 2 +- src/otp.rs | 20 +++++++-------- src/pws.rs | 14 +++++------ src/util.rs | 4 +-- tests/device.rs | 4 +-- tests/otp.rs | 28 ++++++++++----------- tests/pws.rs | 16 ++++++------ tests/util/mod.rs | 7 ++++++ 13 files changed, 123 insertions(+), 80 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index def5273..c34175e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ - Refactor the error handling code: - Implement `std::error::Error` for `CommandError`. - Add the `Error` enum and the `Result` typedef. + - Add the `LibraryError` enum and move the library error variants from + `CommandError` to `LibraryError`. - Return `Error` instead of `CommandError` in all public functions. - Move the `CommandError::RngError` variant to `Error::RandError` and the `CommandError::Unknown` variant to `Error::Unknown`. diff --git a/src/auth.rs b/src/auth.rs index e05f6b3..d1eb049 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -55,7 +55,7 @@ pub trait Authenticate { /// # } /// ``` /// - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`RngError`]: enum.CommandError.html#variant.RngError /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword fn authenticate_user(self, password: &str) -> Result, (Self, Error)> @@ -101,7 +101,7 @@ pub trait Authenticate { /// # } /// ``` /// - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`RngError`]: enum.CommandError.html#variant.RngError /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword fn authenticate_admin(self, password: &str) -> Result, (Self, Error)> @@ -287,7 +287,7 @@ impl Admin { /// # } /// ``` /// - /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot pub fn write_config(&self, config: Config) -> Result<(), Error> { let raw_config = RawConfig::try_from(config)?; unsafe { diff --git a/src/config.rs b/src/config.rs index 741d67e..6aa6d10 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -use crate::error::{CommandError, Error}; +use crate::error::{Error, LibraryError}; /// The configuration for a Nitrokey. #[derive(Clone, Copy, Debug, PartialEq)] @@ -41,7 +41,7 @@ fn option_to_config_otp_slot(value: Option) -> Result { if value < 3 { Ok(value) } else { - Err(CommandError::InvalidSlot.into()) + Err(LibraryError::InvalidSlot.into()) } } None => Ok(255), diff --git a/src/device.rs b/src/device.rs index ccd0597..5c4014b 100644 --- a/src/device.rs +++ b/src/device.rs @@ -461,7 +461,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// # } /// ``` /// - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword fn change_admin_pin(&self, current: &str, new: &str) -> Result<(), Error> { let current_string = get_cstring(current)?; @@ -497,7 +497,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// # } /// ``` /// - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword fn change_user_pin(&self, current: &str, new: &str) -> Result<(), Error> { let current_string = get_cstring(current)?; @@ -533,7 +533,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// # } /// ``` /// - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword fn unlock_user_pin(&self, admin_pin: &str, user_pin: &str) -> Result<(), Error> { let admin_pin_string = get_cstring(admin_pin)?; @@ -867,7 +867,7 @@ impl Storage { /// # } /// ``` /// - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword pub fn change_update_pin(&self, current: &str, new: &str) -> Result<(), Error> { let current_string = get_cstring(current)?; @@ -907,7 +907,7 @@ impl Storage { /// # } /// ``` /// - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword pub fn enable_firmware_update(&self, update_pin: &str) -> Result<(), Error> { let update_pin_string = get_cstring(update_pin)?; @@ -943,7 +943,7 @@ impl Storage { /// # } /// ``` /// - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword pub fn enable_encrypted_volume(&self, user_pin: &str) -> Result<(), Error> { let user_pin = get_cstring(user_pin)?; @@ -1021,7 +1021,7 @@ impl Storage { /// /// [`enable_encrypted_volume`]: #method.enable_encrypted_volume /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString pub fn enable_hidden_volume(&self, volume_password: &str) -> Result<(), Error> { let volume_password = get_cstring(volume_password)?; unsafe { @@ -1099,7 +1099,7 @@ impl Storage { /// ``` /// /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString pub fn create_hidden_volume( &self, slot: u8, @@ -1145,7 +1145,7 @@ impl Storage { /// # } /// ``` /// - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword pub fn set_unencrypted_volume_mode( &self, @@ -1276,7 +1276,7 @@ impl Storage { /// # } /// ``` /// - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword pub fn clear_new_sd_card_warning(&self, admin_pin: &str) -> Result<(), Error> { let admin_pin = get_cstring(admin_pin)?; @@ -1304,7 +1304,7 @@ impl Storage { /// - [`InvalidString`][] if one of the provided passwords contains a null byte /// - [`WrongPassword`][] if the admin password is wrong /// - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword pub fn export_firmware(&self, admin_pin: &str) -> Result<(), Error> { let admin_pin_string = get_cstring(admin_pin)?; diff --git a/src/error.rs b/src/error.rs index c5a975e..f40d07f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -11,6 +11,8 @@ pub enum Error { CommandError(CommandError), /// Placeholder for testing. CommunicationError(CommunicationError), + /// A library usage error. + LibraryError(LibraryError), /// An error that occured during random number generation. RandError(rand_core::Error), /// An unknown error returned by libnitrokey. @@ -21,6 +23,8 @@ impl From for Error { fn from(code: raw::c_int) -> Self { if let Some(err) = CommandError::try_from(code) { Error::CommandError(err) + } else if let Some(err) = LibraryError::try_from(code) { + Error::LibraryError(err) } else { Error::Unknown(code.into()) } @@ -33,6 +37,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: LibraryError) -> Self { + Error::LibraryError(err) + } +} + impl From for Error { fn from(error: rand_core::Error) -> Self { Error::RandError(error) @@ -44,6 +54,7 @@ impl error::Error for Error { match *self { Error::CommandError(ref err) => Some(err), Error::CommunicationError(_) => None, + Error::LibraryError(ref err) => Some(err), Error::RandError(ref err) => Some(err), Error::Unknown(_) => None, } @@ -55,6 +66,7 @@ impl fmt::Display for Error { match *self { Error::CommandError(ref err) => write!(f, "Command error: {}", err), Error::CommunicationError(_) => write!(f, "Placeholder"), + Error::LibraryError(ref err) => write!(f, "Library error: {}", err), Error::RandError(ref err) => write!(f, "RNG error: {}", err), Error::Unknown(ref err) => write!(f, "Unknown error: {}", err), } @@ -90,16 +102,6 @@ pub enum CommandError { AesDecryptionFailed, /// An unspecified error occurred. Undefined, - /// You passed a string containing a null byte. - InvalidString, - /// A supplied string exceeded a length limit. - StringTooLong, - /// You passed an invalid slot. - InvalidSlot, - /// The supplied string was not in hexadecimal format. - InvalidHexString, - /// The target buffer was smaller than the source. - TargetBufferTooSmall, } /// Placeholder for testing. @@ -122,10 +124,6 @@ impl CommandError { 8 => Some(CommandError::NotSupported), 9 => Some(CommandError::UnknownCommand), 10 => Some(CommandError::AesDecryptionFailed), - 200 => Some(CommandError::StringTooLong), - 201 => Some(CommandError::InvalidSlot), - 202 => Some(CommandError::InvalidHexString), - 203 => Some(CommandError::TargetBufferTooSmall), _ => None, } } @@ -149,13 +147,6 @@ impl CommandError { CommandError::UnknownCommand => "This command is unknown".into(), CommandError::AesDecryptionFailed => "AES decryption failed".into(), CommandError::Undefined => "An unspecified error occurred".into(), - CommandError::InvalidString => "You passed a string containing a null byte".into(), - CommandError::StringTooLong => "The supplied string is too long".into(), - CommandError::InvalidSlot => "The given slot is invalid".into(), - CommandError::InvalidHexString => { - "The supplied string is not in hexadecimal format".into() - } - CommandError::TargetBufferTooSmall => "The target buffer is too small".into(), } } } @@ -167,3 +158,44 @@ impl fmt::Display for CommandError { write!(f, "{}", self.as_str()) } } + +/// A library usage error. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum LibraryError { + /// A supplied string exceeded a length limit. + StringTooLong, + /// You passed an invalid slot. + InvalidSlot, + /// The supplied string was not in hexadecimal format. + InvalidHexString, + /// The target buffer was smaller than the source. + TargetBufferTooSmall, + /// You passed a string containing a null byte. + InvalidString, +} + +impl LibraryError { + fn try_from(value: raw::c_int) -> Option { + match value { + 200 => Some(LibraryError::StringTooLong), + 201 => Some(LibraryError::InvalidSlot), + 202 => Some(LibraryError::InvalidHexString), + 203 => Some(LibraryError::TargetBufferTooSmall), + _ => None, + } + } +} + +impl error::Error for LibraryError {} + +impl fmt::Display for LibraryError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match *self { + LibraryError::StringTooLong => "The supplied string is too long", + LibraryError::InvalidSlot => "The given slot is invalid", + LibraryError::InvalidHexString => "The supplied string is not in hexadecimal format", + LibraryError::TargetBufferTooSmall => "The target buffer is too small", + LibraryError::InvalidString => "You passed a string containing a null byte", + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index 8522e83..993ec92 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,7 +104,7 @@ pub use crate::device::{ connect, connect_model, Device, DeviceWrapper, Model, Pro, SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus, }; -pub use crate::error::{CommandError, CommunicationError, Error, Result}; +pub use crate::error::{CommandError, CommunicationError, Error, LibraryError, Result}; pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData}; pub use crate::pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT}; pub use crate::util::LogLevel; diff --git a/src/otp.rs b/src/otp.rs index 5dfe8b1..7535a77 100644 --- a/src/otp.rs +++ b/src/otp.rs @@ -47,8 +47,8 @@ pub trait ConfigureOtp { /// # } /// ``` /// - /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`NoName`]: enum.CommandError.html#variant.NoName fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> Result<(), Error>; @@ -83,8 +83,8 @@ pub trait ConfigureOtp { /// # } /// ``` /// - /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`NoName`]: enum.CommandError.html#variant.NoName fn write_totp_slot(&self, data: OtpSlotData, time_window: u16) -> Result<(), Error>; @@ -115,7 +115,7 @@ pub trait ConfigureOtp { /// # } /// ``` /// - /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot fn erase_hotp_slot(&self, slot: u8) -> Result<(), Error>; /// Erases a TOTP slot. @@ -145,7 +145,7 @@ pub trait ConfigureOtp { /// # } /// ``` /// - /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot fn erase_totp_slot(&self, slot: u8) -> Result<(), Error>; } @@ -216,7 +216,7 @@ pub trait GenerateOtp { /// # } /// ``` /// - /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed fn get_hotp_slot_name(&self, slot: u8) -> Result { unsafe { result_from_string(nitrokey_sys::NK_get_hotp_slot_name(slot)) } @@ -245,7 +245,7 @@ pub trait GenerateOtp { /// # } /// ``` /// - /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed fn get_totp_slot_name(&self, slot: u8) -> Result { unsafe { result_from_string(nitrokey_sys::NK_get_totp_slot_name(slot)) } @@ -275,7 +275,7 @@ pub trait GenerateOtp { /// ``` /// /// [`get_config`]: trait.Device.html#method.get_config - /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot /// [`NotAuthorized`]: enum.CommandError.html#variant.NotAuthorized /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed fn get_hotp_code(&self, slot: u8) -> Result { @@ -320,7 +320,7 @@ pub trait GenerateOtp { /// /// [`set_time`]: #method.set_time /// [`get_config`]: trait.Device.html#method.get_config - /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot /// [`NotAuthorized`]: enum.CommandError.html#variant.NotAuthorized /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed fn get_totp_code(&self, slot: u8) -> Result { diff --git a/src/pws.rs b/src/pws.rs index e974737..47965d7 100644 --- a/src/pws.rs +++ b/src/pws.rs @@ -109,7 +109,7 @@ pub trait GetPasswordSafe { /// [`lock`]: trait.Device.html#method.lock /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed /// [`Device::build_aes_key`]: trait.Device.html#method.build_aes_key - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`Unknown`]: enum.CommandError.html#variant.Unknown /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword fn get_password_safe(&self, user_pin: &str) -> Result, Error>; @@ -207,7 +207,7 @@ impl<'a> PasswordSafe<'a> { /// # } /// ``` /// - /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed pub fn get_slot_name(&self, slot: u8) -> Result { unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_name(slot)) } @@ -240,7 +240,7 @@ impl<'a> PasswordSafe<'a> { /// # } /// ``` /// - /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed pub fn get_slot_login(&self, slot: u8) -> Result { unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_login(slot)) } @@ -273,7 +273,7 @@ impl<'a> PasswordSafe<'a> { /// # } /// ``` /// - /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed pub fn get_slot_password(&self, slot: u8) -> Result { unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_password(slot)) } @@ -304,8 +304,8 @@ impl<'a> PasswordSafe<'a> { /// # } /// ``` /// - /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString pub fn write_slot( &self, slot: u8, @@ -350,7 +350,7 @@ impl<'a> PasswordSafe<'a> { /// # } /// ``` /// - /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot + /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot pub fn erase_slot(&self, slot: u8) -> Result<(), Error> { unsafe { get_command_result(nitrokey_sys::NK_erase_password_safe_slot(slot)) } } diff --git a/src/util.rs b/src/util.rs index 3b9904f..2738fce 100644 --- a/src/util.rs +++ b/src/util.rs @@ -5,7 +5,7 @@ use libc::{c_void, free}; use rand_core::RngCore; use rand_os::OsRng; -use crate::error::{CommandError, Error}; +use crate::error::{CommandError, Error, LibraryError}; /// Log level for libnitrokey. /// @@ -78,7 +78,7 @@ pub fn generate_password(length: usize) -> Result, Error> { } pub fn get_cstring>>(s: T) -> Result { - CString::new(s).or(Err(CommandError::InvalidString.into())) + CString::new(s).or(Err(LibraryError::InvalidString.into())) } impl Into for LogLevel { diff --git a/tests/device.rs b/tests/device.rs index 174624f..ee5dae1 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -6,7 +6,7 @@ use std::{thread, time}; use nitrokey::{ Authenticate, CommandError, Config, ConfigureOtp, Device, Error, GenerateOtp, GetPasswordSafe, - OtpMode, OtpSlotData, Storage, VolumeMode, + LibraryError, OtpMode, OtpSlotData, Storage, VolumeMode, }; use nitrokey_test::test as test_device; @@ -130,7 +130,7 @@ fn config(device: DeviceWrapper) { assert_eq!(config, get_config); let config = Config::new(None, Some(9), None, true); - assert_cmd_err!(CommandError::InvalidSlot, admin.write_config(config)); + assert_lib_err!(LibraryError::InvalidSlot, admin.write_config(config)); let config = Config::new(Some(1), None, Some(0), false); assert_ok!((), admin.write_config(config)); diff --git a/tests/otp.rs b/tests/otp.rs index d328351..51a6539 100644 --- a/tests/otp.rs +++ b/tests/otp.rs @@ -4,8 +4,8 @@ use std::fmt::Debug; use std::ops::Deref; use nitrokey::{ - Admin, Authenticate, CommandError, Config, ConfigureOtp, Device, GenerateOtp, OtpMode, - OtpSlotData, + Admin, Authenticate, CommandError, Config, ConfigureOtp, Device, GenerateOtp, LibraryError, + OtpMode, OtpSlotData, }; use nitrokey_test::test as test_device; @@ -106,7 +106,7 @@ fn hotp_slot_name(device: DeviceWrapper) { let result = device.get_hotp_slot_name(1); assert_eq!("test-hotp", result.unwrap()); let result = device.get_hotp_slot_name(4); - assert_cmd_err!(CommandError::InvalidSlot, result); + assert_lib_err!(LibraryError::InvalidSlot, result); } #[test_device] @@ -115,17 +115,17 @@ fn hotp_error(device: DeviceWrapper) { let slot_data = OtpSlotData::new(1, "", HOTP_SECRET, OtpMode::SixDigits); assert_cmd_err!(CommandError::NoName, admin.write_hotp_slot(slot_data, 0)); let slot_data = OtpSlotData::new(4, "test", HOTP_SECRET, OtpMode::SixDigits); - assert_cmd_err!( - CommandError::InvalidSlot, + assert_lib_err!( + LibraryError::InvalidSlot, admin.write_hotp_slot(slot_data, 0) ); let slot_data = OtpSlotData::new(1, "test", "foobar", OtpMode::SixDigits); - assert_cmd_err!( - CommandError::InvalidHexString, + assert_lib_err!( + LibraryError::InvalidHexString, admin.write_hotp_slot(slot_data, 0) ); let code = admin.get_hotp_code(4); - assert_cmd_err!(CommandError::InvalidSlot, code); + assert_lib_err!(LibraryError::InvalidSlot, code); } #[test_device] @@ -249,7 +249,7 @@ fn totp_slot_name(device: DeviceWrapper) { assert!(result.is_ok()); assert_eq!("test-totp", result.unwrap()); let result = device.get_totp_slot_name(16); - assert_cmd_err!(CommandError::InvalidSlot, result); + assert_lib_err!(LibraryError::InvalidSlot, result); } #[test_device] @@ -258,17 +258,17 @@ fn totp_error(device: DeviceWrapper) { let slot_data = OtpSlotData::new(1, "", TOTP_SECRET, OtpMode::SixDigits); assert_cmd_err!(CommandError::NoName, admin.write_totp_slot(slot_data, 0)); let slot_data = OtpSlotData::new(20, "test", TOTP_SECRET, OtpMode::SixDigits); - assert_cmd_err!( - CommandError::InvalidSlot, + assert_lib_err!( + LibraryError::InvalidSlot, admin.write_totp_slot(slot_data, 0) ); let slot_data = OtpSlotData::new(4, "test", "foobar", OtpMode::SixDigits); - assert_cmd_err!( - CommandError::InvalidHexString, + assert_lib_err!( + LibraryError::InvalidHexString, admin.write_totp_slot(slot_data, 0) ); let code = admin.get_totp_code(20); - assert_cmd_err!(CommandError::InvalidSlot, code); + assert_lib_err!(LibraryError::InvalidSlot, code); } #[test_device] diff --git a/tests/pws.rs b/tests/pws.rs index a4647bd..51e6189 100644 --- a/tests/pws.rs +++ b/tests/pws.rs @@ -3,7 +3,9 @@ mod util; use std::ffi::CStr; use libc::{c_int, c_void, free}; -use nitrokey::{CommandError, Device, Error, GetPasswordSafe, PasswordSafe, SLOT_COUNT}; +use nitrokey::{ + CommandError, Device, Error, GetPasswordSafe, LibraryError, PasswordSafe, SLOT_COUNT, +}; use nitrokey_sys; use nitrokey_test::test as test_device; @@ -104,17 +106,17 @@ fn get_data(device: DeviceWrapper) { assert_eq!(login, pws.get_slot_login(1).unwrap()); assert_eq!(password, pws.get_slot_password(1).unwrap()); - assert_cmd_err!(CommandError::InvalidSlot, pws.get_slot_name(SLOT_COUNT)); - assert_cmd_err!(CommandError::InvalidSlot, pws.get_slot_login(SLOT_COUNT)); - assert_cmd_err!(CommandError::InvalidSlot, pws.get_slot_password(SLOT_COUNT)); + assert_lib_err!(LibraryError::InvalidSlot, pws.get_slot_name(SLOT_COUNT)); + assert_lib_err!(LibraryError::InvalidSlot, pws.get_slot_login(SLOT_COUNT)); + assert_lib_err!(LibraryError::InvalidSlot, pws.get_slot_password(SLOT_COUNT)); } #[test_device] fn write(device: DeviceWrapper) { let pws = get_pws(&device); - assert_cmd_err!( - CommandError::InvalidSlot, + assert_lib_err!( + LibraryError::InvalidSlot, pws.write_slot(SLOT_COUNT, "name", "login", "password") ); @@ -137,7 +139,7 @@ fn write(device: DeviceWrapper) { #[test_device] fn erase(device: DeviceWrapper) { let pws = get_pws(&device); - assert_cmd_err!(CommandError::InvalidSlot, pws.erase_slot(SLOT_COUNT)); + assert_lib_err!(LibraryError::InvalidSlot, pws.erase_slot(SLOT_COUNT)); assert_ok!((), pws.write_slot(0, "name", "login", "password")); assert_ok!((), pws.erase_slot(0)); diff --git a/tests/util/mod.rs b/tests/util/mod.rs index f0d0bb5..b1d3ea3 100644 --- a/tests/util/mod.rs +++ b/tests/util/mod.rs @@ -67,3 +67,10 @@ macro_rules! assert_cmd_err { assert_err!(::nitrokey::Error::CommandError, $left, $right); }; } + +#[macro_export] +macro_rules! assert_lib_err { + ($left:expr, $right:expr) => { + assert_err!(::nitrokey::Error::LibraryError, $left, $right); + }; +} -- cgit v1.2.3 From 27138c4b799248d2d39e9681337a620c89636557 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 17 Jan 2019 13:10:01 +0000 Subject: Add the CommunicationError enum Communication errors returned by libnitrokey were previously not mapped to an error type in the nitrokey crate. We introduce the CommunicationError enum to represent these errors. --- CHANGELOG.md | 2 ++ src/error.rs | 59 +++++++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 51 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index c34175e..413c626 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ - Add the `Error` enum and the `Result` typedef. - Add the `LibraryError` enum and move the library error variants from `CommandError` to `LibraryError`. + - Add the `CommunicationError` enum and move the communication error variants + from `CommandError` to `CommunicationError`. - Return `Error` instead of `CommandError` in all public functions. - Move the `CommandError::RngError` variant to `Error::RandError` and the `CommandError::Unknown` variant to `Error::Unknown`. diff --git a/src/error.rs b/src/error.rs index f40d07f..a2b3848 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,7 +9,7 @@ use std::result; pub enum Error { /// An error reported by the Nitrokey device in the response packet. CommandError(CommandError), - /// Placeholder for testing. + /// A device communication. CommunicationError(CommunicationError), /// A library usage error. LibraryError(LibraryError), @@ -23,6 +23,8 @@ impl From for Error { fn from(code: raw::c_int) -> Self { if let Some(err) = CommandError::try_from(code) { Error::CommandError(err) + } else if let Some(err) = CommunicationError::try_from(256 - code) { + Error::CommunicationError(err) } else if let Some(err) = LibraryError::try_from(code) { Error::LibraryError(err) } else { @@ -37,6 +39,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: CommunicationError) -> Self { + Error::CommunicationError(err) + } +} + impl From for Error { fn from(err: LibraryError) -> Self { Error::LibraryError(err) @@ -53,7 +61,7 @@ impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { Error::CommandError(ref err) => Some(err), - Error::CommunicationError(_) => None, + Error::CommunicationError(ref err) => Some(err), Error::LibraryError(ref err) => Some(err), Error::RandError(ref err) => Some(err), Error::Unknown(_) => None, @@ -65,7 +73,7 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Error::CommandError(ref err) => write!(f, "Command error: {}", err), - Error::CommunicationError(_) => write!(f, "Placeholder"), + Error::CommunicationError(ref err) => write!(f, "Communication error: {}", err), Error::LibraryError(ref err) => write!(f, "Library error: {}", err), Error::RandError(ref err) => write!(f, "RNG error: {}", err), Error::Unknown(ref err) => write!(f, "Unknown error: {}", err), @@ -104,13 +112,6 @@ pub enum CommandError { Undefined, } -/// Placeholder for testing. -#[derive(Debug)] -pub enum CommunicationError { - /// Placeholder for testing. - NotConnected, -} - impl CommandError { fn try_from(value: raw::c_int) -> Option { match value { @@ -159,6 +160,44 @@ impl fmt::Display for CommandError { } } +/// A device communication error. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum CommunicationError { + /// Could not connect to a Nitrokey device. + NotConnected, + /// Sending a packet failed. + SendingFailure, + /// Receiving a packet failed. + ReceivingFailure, + /// A packet with a wrong checksum was received. + InvalidCrc, +} + +impl CommunicationError { + fn try_from(value: raw::c_int) -> Option { + match value { + 2 => Some(CommunicationError::NotConnected), + 3 => Some(CommunicationError::SendingFailure), + 4 => Some(CommunicationError::ReceivingFailure), + 5 => Some(CommunicationError::InvalidCrc), + _ => None, + } + } +} + +impl error::Error for CommunicationError {} + +impl fmt::Display for CommunicationError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match *self { + CommunicationError::NotConnected => "Could not connect to a Nitrokey device", + CommunicationError::SendingFailure => "Sending a packet failed", + CommunicationError::ReceivingFailure => "Receiving a packet failed", + CommunicationError::InvalidCrc => "A packet with a wrong checksum was received", + }) + } +} + /// A library usage error. #[derive(Clone, Copy, Debug, PartialEq)] pub enum LibraryError { -- cgit v1.2.3 From 391cfd03edafd6e857d6cdbee1347f38e7a02b3f Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 17 Jan 2019 13:28:03 +0000 Subject: Remove CommandError::as_str method AsStr is automatically implementeded if Display is implemented, so having a manual as_str() method is not necessary. --- src/error.rs | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/error.rs b/src/error.rs index a2b3848..1aaf21f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,3 @@ -use std::borrow; use std::error; use std::fmt; use std::os::raw; @@ -128,35 +127,28 @@ impl CommandError { _ => None, } } - - fn as_str(&self) -> borrow::Cow<'static, str> { - match *self { - CommandError::WrongCrc => { - "A packet with a wrong checksum has been sent or received".into() - } - CommandError::WrongSlot => "The given OTP slot does not exist".into(), - CommandError::SlotNotProgrammed => "The given OTP slot is not programmed".into(), - CommandError::WrongPassword => "The given password is wrong".into(), - CommandError::NotAuthorized => { - "You are not authorized for this command or provided a wrong temporary \ - password" - .into() - } - CommandError::Timestamp => "An error occurred when getting or setting the time".into(), - CommandError::NoName => "You did not provide a name for the OTP slot".into(), - CommandError::NotSupported => "This command is not supported by this device".into(), - CommandError::UnknownCommand => "This command is unknown".into(), - CommandError::AesDecryptionFailed => "AES decryption failed".into(), - CommandError::Undefined => "An unspecified error occurred".into(), - } - } } impl error::Error for CommandError {} impl fmt::Display for CommandError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.as_str()) + f.write_str(match *self { + CommandError::WrongCrc => "A packet with a wrong checksum has been sent or received", + CommandError::WrongSlot => "The given OTP slot does not exist", + CommandError::SlotNotProgrammed => "The given OTP slot is not programmed", + CommandError::WrongPassword => "The given password is wrong", + CommandError::NotAuthorized => { + "You are not authorized for this command or provided a wrong temporary \ + password" + } + CommandError::Timestamp => "An error occurred when getting or setting the time", + CommandError::NoName => "You did not provide a name for the OTP slot", + CommandError::NotSupported => "This command is not supported by this device", + CommandError::UnknownCommand => "This command is unknown", + CommandError::AesDecryptionFailed => "AES decryption failed", + CommandError::Undefined => "An unspecified error occurred", + }) } } -- cgit v1.2.3 From c3e551dd40142bcd2552972d549f31ad7483621d Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 17 Jan 2019 13:30:20 +0000 Subject: Make CommandError messages more general For example, the WrongSlot error may also be returned for a PWS slot. --- src/error.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/error.rs b/src/error.rs index 1aaf21f..ef9b149 100644 --- a/src/error.rs +++ b/src/error.rs @@ -135,15 +135,15 @@ impl fmt::Display for CommandError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match *self { CommandError::WrongCrc => "A packet with a wrong checksum has been sent or received", - CommandError::WrongSlot => "The given OTP slot does not exist", - CommandError::SlotNotProgrammed => "The given OTP slot is not programmed", + CommandError::WrongSlot => "The given slot does not exist", + CommandError::SlotNotProgrammed => "The given slot is not programmed", CommandError::WrongPassword => "The given password is wrong", CommandError::NotAuthorized => { "You are not authorized for this command or provided a wrong temporary \ password" } CommandError::Timestamp => "An error occurred when getting or setting the time", - CommandError::NoName => "You did not provide a name for the OTP slot", + CommandError::NoName => "You did not provide a name for the slot", CommandError::NotSupported => "This command is not supported by this device", CommandError::UnknownCommand => "This command is unknown", CommandError::AesDecryptionFailed => "AES decryption failed", -- cgit v1.2.3 From c191e875492ff8aeab1b4493b87486cd265f0edc Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 17 Jan 2019 13:38:28 +0000 Subject: Introduce the Error::UnexpectedError variant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The UnexpectedError variant is used when a libnitrokey function returns a value that violates the function’s contract, for example if a function returns a null pointer although it guarantees to never return null. Previously, we returned a CommandError::Unspecified in these cases. --- src/error.rs | 4 ++++ src/util.rs | 6 +++--- tests/pws.rs | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/error.rs b/src/error.rs index ef9b149..b27124c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,6 +14,8 @@ pub enum Error { LibraryError(LibraryError), /// An error that occured during random number generation. RandError(rand_core::Error), + /// An error that is caused by an unexpected value returned by libnitrokey. + UnexpectedError, /// An unknown error returned by libnitrokey. Unknown(i64), } @@ -63,6 +65,7 @@ impl error::Error for Error { Error::CommunicationError(ref err) => Some(err), Error::LibraryError(ref err) => Some(err), Error::RandError(ref err) => Some(err), + Error::UnexpectedError => None, Error::Unknown(_) => None, } } @@ -75,6 +78,7 @@ impl fmt::Display for Error { Error::CommunicationError(ref err) => write!(f, "Communication error: {}", err), Error::LibraryError(ref err) => write!(f, "Library error: {}", err), Error::RandError(ref err) => write!(f, "RNG error: {}", err), + Error::UnexpectedError => write!(f, "An unexpected error occurred"), Error::Unknown(ref err) => write!(f, "Unknown error: {}", err), } } diff --git a/src/util.rs b/src/util.rs index 2738fce..79b8c34 100644 --- a/src/util.rs +++ b/src/util.rs @@ -5,7 +5,7 @@ use libc::{c_void, free}; use rand_core::RngCore; use rand_os::OsRng; -use crate::error::{CommandError, Error, LibraryError}; +use crate::error::{Error, LibraryError}; /// Log level for libnitrokey. /// @@ -36,7 +36,7 @@ pub fn owned_str_from_ptr(ptr: *const c_char) -> String { pub fn result_from_string(ptr: *const c_char) -> Result { if ptr.is_null() { - return Err(CommandError::Undefined.into()); + return Err(Error::UnexpectedError); } unsafe { let s = owned_str_from_ptr(ptr); @@ -65,7 +65,7 @@ pub fn get_last_result() -> Result<(), Error> { pub fn get_last_error() -> Error { return match get_last_result() { - Ok(()) => CommandError::Undefined.into(), + Ok(()) => Error::UnexpectedError, Err(err) => err, }; } diff --git a/tests/pws.rs b/tests/pws.rs index 51e6189..b89d7f6 100644 --- a/tests/pws.rs +++ b/tests/pws.rs @@ -14,7 +14,7 @@ use crate::util::{ADMIN_PASSWORD, USER_PASSWORD}; fn get_slot_name_direct(slot: u8) -> Result { let ptr = unsafe { nitrokey_sys::NK_get_password_safe_slot_name(slot) }; if ptr.is_null() { - return Err(CommandError::Undefined.into()); + return Err(Error::UnexpectedError); } let s = unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() }; unsafe { free(ptr as *mut c_void) }; -- cgit v1.2.3 From 70e886d3ca487c306b8eced9f0e067a67ba9c1bb Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 17 Jan 2019 13:53:15 +0000 Subject: Return CommunicationError::NotConnected from connect functions Previously, we returned a CommandError::Undefined if a connect function failed. A CommunicationError::NotConnected is a more specific and better fitting choice. Once the Try trait has been stabilized, we should return an Option<_> instead of a Result<_, Error> from the connect functions. --- CHANGELOG.md | 2 ++ src/device.rs | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 413c626..1856336 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ - Return `Error` instead of `CommandError` in all public functions. - Move the `CommandError::RngError` variant to `Error::RandError` and the `CommandError::Unknown` variant to `Error::Unknown`. + - Return `CommunicationError::NotConnected` instead of + `CommandError::Undefined` from the connect functions. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/device.rs b/src/device.rs index 5c4014b..1cf9da9 100644 --- a/src/device.rs +++ b/src/device.rs @@ -5,7 +5,7 @@ use nitrokey_sys; use crate::auth::Authenticate; use crate::config::{Config, RawConfig}; -use crate::error::{CommandError, Error}; +use crate::error::{CommunicationError, Error}; use crate::otp::GenerateOtp; use crate::pws::GetPasswordSafe; use crate::util::{get_command_result, get_cstring, get_last_error, result_from_string}; @@ -644,7 +644,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// /// # Errors /// -/// - [`Undefined`][] if no Nitrokey device is connected +/// - [`NotConnected`][] if no Nitrokey device is connected /// /// # Example /// @@ -659,15 +659,15 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { /// } /// ``` /// -/// [`Undefined`]: enum.CommandError.html#variant.Undefined +/// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected pub fn connect() -> Result { unsafe { match nitrokey_sys::NK_login_auto() { 1 => match get_connected_device() { Some(wrapper) => Ok(wrapper), - None => Err(CommandError::Undefined.into()), + None => Err(CommunicationError::NotConnected.into()), }, - _ => Err(CommandError::Undefined.into()), + _ => Err(CommunicationError::NotConnected.into()), } } } @@ -676,7 +676,7 @@ pub fn connect() -> Result { /// /// # Errors /// -/// - [`Undefined`][] if no Nitrokey device of the given model is connected +/// - [`NotConnected`][] if no Nitrokey device of the given model is connected /// /// # Example /// @@ -692,12 +692,12 @@ pub fn connect() -> Result { /// } /// ``` /// -/// [`Undefined`]: enum.CommandError.html#variant.Undefined +/// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected pub fn connect_model(model: Model) -> Result { if connect_enum(model) { Ok(create_device_wrapper(model)) } else { - Err(CommandError::Undefined.into()) + Err(CommunicationError::NotConnected.into()) } } @@ -771,7 +771,7 @@ impl Pro { /// /// # Errors /// - /// - [`Undefined`][] if no Nitrokey device of the given model is connected + /// - [`NotConnected`][] if no Nitrokey device of the given model is connected /// /// # Example /// @@ -786,12 +786,12 @@ impl Pro { /// } /// ``` /// - /// [`Undefined`]: enum.CommandError.html#variant.Undefined + /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected pub fn connect() -> Result { // TODO: maybe Option instead of Result? match connect_enum(Model::Pro) { true => Ok(Pro {}), - false => Err(CommandError::Undefined.into()), + false => Err(CommunicationError::NotConnected.into()), } } } @@ -817,7 +817,7 @@ impl Storage { /// /// # Errors /// - /// - [`Undefined`][] if no Nitrokey device of the given model is connected + /// - [`NotConnected`][] if no Nitrokey device of the given model is connected /// /// # Example /// @@ -832,12 +832,12 @@ impl Storage { /// } /// ``` /// - /// [`Undefined`]: enum.CommandError.html#variant.Undefined + /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected pub fn connect() -> Result { // TODO: maybe Option instead of Result? match connect_enum(Model::Storage) { true => Ok(Storage {}), - false => Err(CommandError::Undefined.into()), + false => Err(CommunicationError::NotConnected.into()), } } -- cgit v1.2.3 From 17f9c30a0ace070cba856e4e89fcccedcab5e8e6 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 17 Jan 2019 14:00:54 +0000 Subject: Remove the unused CommandError::Undefined variant The CommandError::Undefined variant has been refactored into Error::UnexpectedError and CommunicationError::NotConnected and is therefore no longer needed. --- CHANGELOG.md | 1 + src/error.rs | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 1856336..a4df5c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ `CommandError::Unknown` variant to `Error::Unknown`. - Return `CommunicationError::NotConnected` instead of `CommandError::Undefined` from the connect functions. + - Remove the `CommandError::Undefined` variant. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/error.rs b/src/error.rs index b27124c..cde9a34 100644 --- a/src/error.rs +++ b/src/error.rs @@ -111,8 +111,6 @@ pub enum CommandError { UnknownCommand, /// AES decryption failed. AesDecryptionFailed, - /// An unspecified error occurred. - Undefined, } impl CommandError { @@ -151,7 +149,6 @@ impl fmt::Display for CommandError { CommandError::NotSupported => "This command is not supported by this device", CommandError::UnknownCommand => "This command is unknown", CommandError::AesDecryptionFailed => "AES decryption failed", - CommandError::Undefined => "An unspecified error occurred", }) } } -- cgit v1.2.3 From d87859975dc158919ecd5bf11a1111a2da5fcb30 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 17 Jan 2019 14:21:44 +0000 Subject: Check specific error codes in the tests If possible, check specific error codes instead of `is_err()`. This makes the code more readable and catches bugs resulting in the wrong error code. Also, using the assert_*_err and assert_ok macros yields error messages containing the expected and the actual value. To be able to use these macros with the `get_password_safe` method, we also have to implement `Debug` for `PasswordSafe` and `Device`. --- TODO.md | 1 - src/device.rs | 2 +- src/pws.rs | 1 + tests/device.rs | 46 +++++++++++++++------------------------------- tests/otp.rs | 20 ++++++-------------- tests/pws.rs | 6 ++---- tests/util/mod.rs | 7 +++++++ 7 files changed, 32 insertions(+), 51 deletions(-) (limited to 'src') diff --git a/TODO.md b/TODO.md index 53de7e9..487f56d 100644 --- a/TODO.md +++ b/TODO.md @@ -9,7 +9,6 @@ - Clear passwords from memory. - Find a nicer syntax for the `write_config` test. - Prevent construction of internal types. -- More specific error checking in the tests. - Check integer conversions. - Consider implementing `Into` for `(Device, CommandError)` - Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware diff --git a/src/device.rs b/src/device.rs index 1cf9da9..16064c3 100644 --- a/src/device.rs +++ b/src/device.rs @@ -286,7 +286,7 @@ pub struct StorageStatus { /// /// This trait provides the commands that can be executed without authentication and that are /// present on all supported Nitrokey devices. -pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { +pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// Returns the model of the connected Nitrokey device. /// /// # Example diff --git a/src/pws.rs b/src/pws.rs index 47965d7..a21527c 100644 --- a/src/pws.rs +++ b/src/pws.rs @@ -52,6 +52,7 @@ pub const SLOT_COUNT: u8 = 16; /// [`get_password_safe`]: trait.GetPasswordSafe.html#method.get_password_safe /// [`lock`]: trait.Device.html#method.lock /// [`GetPasswordSafe`]: trait.GetPasswordSafe.html +#[derive(Debug)] pub struct PasswordSafe<'a> { _device: &'a dyn Device, } diff --git a/tests/device.rs b/tests/device.rs index ee5dae1..c502945 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -5,8 +5,8 @@ use std::process::Command; use std::{thread, time}; use nitrokey::{ - Authenticate, CommandError, Config, ConfigureOtp, Device, Error, GenerateOtp, GetPasswordSafe, - LibraryError, OtpMode, OtpSlotData, Storage, VolumeMode, + Authenticate, CommandError, CommunicationError, Config, ConfigureOtp, Device, Error, + GenerateOtp, GetPasswordSafe, LibraryError, OtpMode, OtpSlotData, Storage, VolumeMode, }; use nitrokey_test::test as test_device; @@ -31,11 +31,11 @@ fn count_nitrokey_block_devices() -> usize { #[test_device] fn connect_no_device() { - assert!(nitrokey::connect().is_err()); - assert!(nitrokey::connect_model(nitrokey::Model::Pro).is_err()); - assert!(nitrokey::connect_model(nitrokey::Model::Storage).is_err()); - assert!(nitrokey::Pro::connect().is_err()); - assert!(nitrokey::Storage::connect().is_err()); + assert_cmu_err!(CommunicationError::NotConnected, nitrokey::connect()); + assert_cmu_err!(CommunicationError::NotConnected, nitrokey::connect_model(nitrokey::Model::Pro)); + assert_cmu_err!(CommunicationError::NotConnected, nitrokey::connect_model(nitrokey::Model::Storage)); + assert_cmu_err!(CommunicationError::NotConnected, nitrokey::Pro::connect()); + assert_cmu_err!(CommunicationError::NotConnected, nitrokey::Storage::connect()); } #[test_device] @@ -148,9 +148,7 @@ fn change_user_pin(device: DeviceWrapper) { let device = device.authenticate_user(USER_PASSWORD).unwrap().device(); let device = device.authenticate_user(USER_NEW_PASSWORD).unwrap_err().0; - assert!(device - .change_user_pin(USER_PASSWORD, USER_NEW_PASSWORD) - .is_ok()); + assert_ok!((), device.change_user_pin(USER_PASSWORD, USER_NEW_PASSWORD)); let device = device.authenticate_user(USER_PASSWORD).unwrap_err().0; let device = device @@ -161,9 +159,7 @@ fn change_user_pin(device: DeviceWrapper) { let result = device.change_user_pin(USER_PASSWORD, USER_PASSWORD); assert_cmd_err!(CommandError::WrongPassword, result); - assert!(device - .change_user_pin(USER_NEW_PASSWORD, USER_PASSWORD) - .is_ok()); + assert_ok!((), device.change_user_pin(USER_NEW_PASSWORD, USER_PASSWORD)); let device = device.authenticate_user(USER_PASSWORD).unwrap().device(); assert!(device.authenticate_user(USER_NEW_PASSWORD).is_err()); @@ -174,9 +170,7 @@ fn change_admin_pin(device: DeviceWrapper) { let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device(); let device = device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err().0; - assert!(device - .change_admin_pin(ADMIN_PASSWORD, ADMIN_NEW_PASSWORD) - .is_ok()); + assert_ok!((), device.change_admin_pin(ADMIN_PASSWORD, ADMIN_NEW_PASSWORD)); let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap_err().0; let device = device @@ -189,9 +183,7 @@ fn change_admin_pin(device: DeviceWrapper) { device.change_admin_pin(ADMIN_PASSWORD, ADMIN_PASSWORD) ); - assert!(device - .change_admin_pin(ADMIN_NEW_PASSWORD, ADMIN_PASSWORD) - .is_ok()); + assert_ok!((), device.change_admin_pin(ADMIN_NEW_PASSWORD, ADMIN_PASSWORD)); let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device(); device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err(); @@ -215,9 +207,7 @@ where #[test_device] fn unlock_user_pin(device: DeviceWrapper) { let device = device.authenticate_user(USER_PASSWORD).unwrap().device(); - assert!(device - .unlock_user_pin(ADMIN_PASSWORD, USER_PASSWORD) - .is_ok()); + assert_ok!((), device.unlock_user_pin(ADMIN_PASSWORD, USER_PASSWORD)); assert_cmd_err!( CommandError::WrongPassword, device.unlock_user_pin(USER_PASSWORD, USER_PASSWORD) @@ -235,9 +225,7 @@ fn unlock_user_pin(device: DeviceWrapper) { CommandError::WrongPassword, device.unlock_user_pin(USER_PASSWORD, USER_PASSWORD) ); - assert!(device - .unlock_user_pin(ADMIN_PASSWORD, USER_PASSWORD) - .is_ok()); + assert_ok!((), device.unlock_user_pin(ADMIN_PASSWORD, USER_PASSWORD)); let device = device.authenticate_user(USER_PASSWORD).unwrap().device(); // block user PIN @@ -251,14 +239,10 @@ fn unlock_user_pin(device: DeviceWrapper) { CommandError::WrongPassword, device.unlock_user_pin(USER_PASSWORD, USER_PASSWORD) ); - assert!(device - .unlock_user_pin(ADMIN_PASSWORD, USER_NEW_PASSWORD) - .is_ok()); + assert_ok!((), device.unlock_user_pin(ADMIN_PASSWORD, USER_NEW_PASSWORD)); // reset user PIN - assert!(device - .change_user_pin(USER_NEW_PASSWORD, USER_PASSWORD) - .is_ok()); + assert_ok!((), device.change_user_pin(USER_NEW_PASSWORD, USER_PASSWORD)); } #[test_device] diff --git a/tests/otp.rs b/tests/otp.rs index 51a6539..96da371 100644 --- a/tests/otp.rs +++ b/tests/otp.rs @@ -93,7 +93,7 @@ fn hotp_pin(device: DeviceWrapper) { let user = admin.device().authenticate_user(USER_PASSWORD).unwrap(); check_hotp_codes(&user, 0); - assert!(user.device().get_hotp_code(1).is_err()); + assert_cmd_err!(CommandError::NotAuthorized, user.device().get_hotp_code(1)); } #[test_device] @@ -156,7 +156,7 @@ fn configure_totp(admin: &ConfigureOtp, factor: u64) { } fn check_totp_codes(device: &GenerateOtp, factor: u64, timestamp_size: TotpTimestampSize) { - for (i, &(base_time, code)) in TOTP_CODES.iter().enumerate() { + for (base_time, code) in TOTP_CODES { let time = base_time.checked_mul(factor).unwrap(); let is_u64 = time > u32::max_value() as u64; if is_u64 != (timestamp_size == TotpTimestampSize::U64) { @@ -164,14 +164,7 @@ fn check_totp_codes(device: &GenerateOtp, factor: u64, timestamp_size: TotpTimes } assert_ok!((), device.set_time(time, true)); - let result = device.get_totp_code(1); - assert!(result.is_ok()); - let result_code = result.unwrap(); - assert_eq!( - code, result_code, - "TOTP code {} should be {} but is {}", - i, code, result_code - ); + assert_ok!(code.to_string(), device.get_totp_code(1)); } } @@ -221,7 +214,7 @@ fn totp_pin(device: DeviceWrapper) { let user = admin.device().authenticate_user(USER_PASSWORD).unwrap(); check_totp_codes(&user, 1, TotpTimestampSize::U32); - assert!(user.device().get_totp_code(1).is_err()); + assert_cmd_err!(CommandError::NotAuthorized, user.device().get_totp_code(1)); } #[test_device] @@ -235,7 +228,7 @@ fn totp_pin_64(device: Pro) { let user = admin.device().authenticate_user(USER_PASSWORD).unwrap(); check_totp_codes(&user, 1, TotpTimestampSize::U64); - assert!(user.device().get_totp_code(1).is_err()); + assert_cmd_err!(CommandError::NotAuthorized, user.device().get_totp_code(1)); } #[test_device] @@ -246,8 +239,7 @@ fn totp_slot_name(device: DeviceWrapper) { let device = admin.device(); let result = device.get_totp_slot_name(1); - assert!(result.is_ok()); - assert_eq!("test-totp", result.unwrap()); + assert_ok!("test-totp", result); let result = device.get_totp_slot_name(16); assert_lib_err!(LibraryError::InvalidSlot, result); } diff --git a/tests/pws.rs b/tests/pws.rs index b89d7f6..7a97983 100644 --- a/tests/pws.rs +++ b/tests/pws.rs @@ -39,11 +39,9 @@ where #[test_device] fn enable(device: DeviceWrapper) { - assert!(device - .get_password_safe(&(USER_PASSWORD.to_owned() + "123")) - .is_err()); + assert_cmd_err!(CommandError::WrongPassword, device.get_password_safe(&(USER_PASSWORD.to_owned() + "123"))); assert!(device.get_password_safe(USER_PASSWORD).is_ok()); - assert!(device.get_password_safe(ADMIN_PASSWORD).is_err()); + assert_cmd_err!(CommandError::WrongPassword, device.get_password_safe(ADMIN_PASSWORD)); assert!(device.get_password_safe(USER_PASSWORD).is_ok()); } diff --git a/tests/util/mod.rs b/tests/util/mod.rs index b1d3ea3..4a00a66 100644 --- a/tests/util/mod.rs +++ b/tests/util/mod.rs @@ -68,6 +68,13 @@ macro_rules! assert_cmd_err { }; } +#[macro_export] +macro_rules! assert_cmu_err { + ($left:expr, $right:expr) => { + assert_err!(::nitrokey::Error::CommunicationError, $left, $right); + }; +} + #[macro_export] macro_rules! assert_lib_err { ($left:expr, $right:expr) => { -- cgit v1.2.3 From 57e3c6bf010d51842cbc86a9801fd9baee1b22eb Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 23 Jan 2019 02:29:00 +0000 Subject: Prevent direct instantiation of Pro and Storage The Pro and Storage structs may only be created using the connect functions. This patch adds a private PhantomData field to the structs to ensure that the compiler does not allow direct instantiation. --- CHANGELOG.md | 2 ++ TODO.md | 1 - src/device.rs | 29 +++++++++++++++++++++++------ 3 files changed, 25 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index a4df5c6..1e962e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ - Return `CommunicationError::NotConnected` instead of `CommandError::Undefined` from the connect functions. - Remove the `CommandError::Undefined` variant. +- Add a private `PhantomData` field to `Pro` and `Storage` to make direct + instantiation impossible. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/TODO.md b/TODO.md index 487f56d..8f4e348 100644 --- a/TODO.md +++ b/TODO.md @@ -8,7 +8,6 @@ - Fix timing issues with the `totp_no_pin` and `totp_pin` test cases. - Clear passwords from memory. - Find a nicer syntax for the `write_config` test. -- Prevent construction of internal types. - Check integer conversions. - Consider implementing `Into` for `(Device, CommandError)` - Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware diff --git a/src/device.rs b/src/device.rs index 16064c3..40d6ba4 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,4 +1,5 @@ use std::fmt; +use std::marker; use libc; use nitrokey_sys; @@ -154,7 +155,11 @@ pub enum DeviceWrapper { /// [`connect`]: fn.connect.html /// [`Pro::connect`]: #method.connect #[derive(Debug)] -pub struct Pro {} +pub struct Pro { + // make sure that users cannot directly instantiate this type + #[doc(hidden)] + marker: marker::PhantomData<()>, +} /// A Nitrokey Storage device without user or admin authentication. /// @@ -196,7 +201,11 @@ pub struct Pro {} /// [`connect`]: fn.connect.html /// [`Storage::connect`]: #method.connect #[derive(Debug)] -pub struct Storage {} +pub struct Storage { + // make sure that users cannot directly instantiate this type + #[doc(hidden)] + marker: marker::PhantomData<()>, +} /// The status of a volume on a Nitrokey Storage device. #[derive(Debug)] @@ -713,8 +722,12 @@ fn get_connected_model() -> Option { fn create_device_wrapper(model: Model) -> DeviceWrapper { match model { - Model::Pro => DeviceWrapper::Pro(Pro {}), - Model::Storage => DeviceWrapper::Storage(Storage {}), + Model::Pro => DeviceWrapper::Pro(Pro { + marker: marker::PhantomData, + }), + Model::Storage => DeviceWrapper::Storage(Storage { + marker: marker::PhantomData, + }), } } @@ -790,7 +803,9 @@ impl Pro { pub fn connect() -> Result { // TODO: maybe Option instead of Result? match connect_enum(Model::Pro) { - true => Ok(Pro {}), + true => Ok(Pro { + marker: marker::PhantomData, + }), false => Err(CommunicationError::NotConnected.into()), } } @@ -836,7 +851,9 @@ impl Storage { pub fn connect() -> Result { // TODO: maybe Option instead of Result? match connect_enum(Model::Storage) { - true => Ok(Storage {}), + true => Ok(Storage { + marker: marker::PhantomData, + }), false => Err(CommunicationError::NotConnected.into()), } } -- cgit v1.2.3 From 601bc22ae18838ff56b64c15b365bcf7f93006be Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 23 Jan 2019 02:44:20 +0000 Subject: Prefer into() over numeric casting Numeric casting might truncate an integer, while into() is only implemented for numeric types if the cast is possible without truncation. --- CHANGELOG.md | 1 + TODO.md | 1 - src/util.rs | 3 +-- 3 files changed, 2 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e962e0..5466620 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - Remove the `CommandError::Undefined` variant. - Add a private `PhantomData` field to `Pro` and `Storage` to make direct instantiation impossible. +- Prefer using the `Into` trait over numeric casting. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/TODO.md b/TODO.md index 8f4e348..0467f1d 100644 --- a/TODO.md +++ b/TODO.md @@ -8,7 +8,6 @@ - Fix timing issues with the `totp_no_pin` and `totp_pin` test cases. - Clear passwords from memory. - Find a nicer syntax for the `write_config` test. -- Check integer conversions. - Consider implementing `Into` for `(Device, CommandError)` - Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware issue 65][]). diff --git a/src/util.rs b/src/util.rs index 79b8c34..2542a7b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -59,8 +59,7 @@ pub fn get_command_result(value: c_int) -> Result<(), Error> { } pub fn get_last_result() -> Result<(), Error> { - let value = unsafe { nitrokey_sys::NK_get_last_command_status() } as c_int; - get_command_result(value) + get_command_result(unsafe { nitrokey_sys::NK_get_last_command_status() }.into()) } pub fn get_last_error() -> Error { -- cgit v1.2.3 From e31009064eaaef9153ad5da3911aa0a939a050c2 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 23 Jan 2019 03:02:31 +0000 Subject: Add temp_password_ptr method to AuthenticatedDevice To reduce the number of casts, we introduce the temp_password_ptr method that casts the pointer received from the Vec to a c_char pointer that can be handled by libnitrokey. --- src/auth.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/auth.rs b/src/auth.rs index d1eb049..541de35 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -111,6 +111,8 @@ pub trait Authenticate { trait AuthenticatedDevice { fn new(device: T, temp_password: Vec) -> Self; + + fn temp_password_ptr(&self) -> *const c_char; } /// A Nitrokey device with user authentication. @@ -217,20 +219,19 @@ impl Deref for User { impl GenerateOtp for User { fn get_hotp_code(&self, slot: u8) -> Result { unsafe { - let temp_password_ptr = self.temp_password.as_ptr() as *const c_char; + let temp_password_ptr = self.temp_password_ptr(); return result_from_string(nitrokey_sys::NK_get_hotp_code_PIN(slot, temp_password_ptr)); } } fn get_totp_code(&self, slot: u8) -> Result { unsafe { - let temp_password_ptr = self.temp_password.as_ptr() as *const c_char; return result_from_string(nitrokey_sys::NK_get_totp_code_PIN( slot, 0, 0, 0, - temp_password_ptr, + self.temp_password_ptr(), )); } } @@ -243,6 +244,10 @@ impl AuthenticatedDevice for User { temp_password, } } + + fn temp_password_ptr(&self) -> *const c_char { + self.temp_password.as_ptr() as *const c_char + } } impl Deref for Admin { @@ -297,7 +302,7 @@ impl Admin { raw_config.scrollock, raw_config.user_password, false, - self.temp_password.as_ptr() as *const c_char, + self.temp_password_ptr(), )) } } @@ -307,8 +312,7 @@ impl Admin { C: Fn(RawOtpSlotData, *const c_char) -> c_int, { let raw_data = RawOtpSlotData::new(data)?; - let temp_password_ptr = self.temp_password.as_ptr() as *const c_char; - get_command_result(callback(raw_data, temp_password_ptr)) + get_command_result(callback(raw_data, self.temp_password_ptr())) } } @@ -346,12 +350,12 @@ impl ConfigureOtp for Admin { } fn erase_hotp_slot(&self, slot: u8) -> Result<(), Error> { - let temp_password_ptr = self.temp_password.as_ptr() as *const c_char; + let temp_password_ptr = self.temp_password_ptr(); unsafe { get_command_result(nitrokey_sys::NK_erase_hotp_slot(slot, temp_password_ptr)) } } fn erase_totp_slot(&self, slot: u8) -> Result<(), Error> { - let temp_password_ptr = self.temp_password.as_ptr() as *const c_char; + let temp_password_ptr = self.temp_password_ptr(); unsafe { get_command_result(nitrokey_sys::NK_erase_totp_slot(slot, temp_password_ptr)) } } } @@ -363,6 +367,10 @@ impl AuthenticatedDevice for Admin { temp_password, } } + + fn temp_password_ptr(&self) -> *const c_char { + self.temp_password.as_ptr() as *const c_char + } } impl Authenticate for DeviceWrapper { -- cgit v1.2.3 From 5540ca5e76ffe5efe27d8819efb9e62066a10219 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 23 Jan 2019 03:46:09 +0000 Subject: Refactor and clean up all code This includes: - using idiomatic Rust - limiting the scope of unsafe blocks - simplifying code --- CHANGELOG.md | 3 +- src/auth.rs | 59 ++++++++++-------------- src/config.rs | 20 ++++----- src/device.rs | 141 +++++++++++++++++++++++----------------------------------- src/otp.rs | 12 ++--- src/pws.rs | 24 +++++----- src/util.rs | 35 +++++++-------- 7 files changed, 123 insertions(+), 171 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 5466620..365b864 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,8 @@ - Remove the `CommandError::Undefined` variant. - Add a private `PhantomData` field to `Pro` and `Storage` to make direct instantiation impossible. -- Prefer using the `Into` trait over numeric casting. +- Refactor and clean up internal code: + - Prefer using the `Into` trait over numeric casting. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/auth.rs b/src/auth.rs index 541de35..b97bee6 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -161,10 +161,10 @@ where }; let password_ptr = password.as_ptr(); let temp_password_ptr = temp_password.as_ptr() as *const c_char; - return match callback(password_ptr, temp_password_ptr) { + match callback(password_ptr, temp_password_ptr) { 0 => Ok(A::new(device, temp_password)), rv => Err((device, Error::from(rv))), - }; + } } fn authenticate_user_wrapper( @@ -218,22 +218,15 @@ impl Deref for User { impl GenerateOtp for User { fn get_hotp_code(&self, slot: u8) -> Result { - unsafe { - let temp_password_ptr = self.temp_password_ptr(); - return result_from_string(nitrokey_sys::NK_get_hotp_code_PIN(slot, temp_password_ptr)); - } + result_from_string(unsafe { + nitrokey_sys::NK_get_hotp_code_PIN(slot, self.temp_password_ptr()) + }) } fn get_totp_code(&self, slot: u8) -> Result { - unsafe { - return result_from_string(nitrokey_sys::NK_get_totp_code_PIN( - slot, - 0, - 0, - 0, - self.temp_password_ptr(), - )); - } + result_from_string(unsafe { + nitrokey_sys::NK_get_totp_code_PIN(slot, 0, 0, 0, self.temp_password_ptr()) + }) } } @@ -295,30 +288,23 @@ impl Admin { /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot pub fn write_config(&self, config: Config) -> Result<(), Error> { let raw_config = RawConfig::try_from(config)?; - unsafe { - get_command_result(nitrokey_sys::NK_write_config( + get_command_result(unsafe { + nitrokey_sys::NK_write_config( raw_config.numlock, raw_config.capslock, raw_config.scrollock, raw_config.user_password, false, self.temp_password_ptr(), - )) - } - } - - fn write_otp_slot(&self, data: OtpSlotData, callback: C) -> Result<(), Error> - where - C: Fn(RawOtpSlotData, *const c_char) -> c_int, - { - let raw_data = RawOtpSlotData::new(data)?; - get_command_result(callback(raw_data, self.temp_password_ptr())) + ) + }) } } impl ConfigureOtp for Admin { fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> Result<(), Error> { - self.write_otp_slot(data, |raw_data: RawOtpSlotData, temp_password_ptr| unsafe { + let raw_data = RawOtpSlotData::new(data)?; + get_command_result(unsafe { nitrokey_sys::NK_write_hotp_slot( raw_data.number, raw_data.name.as_ptr(), @@ -328,13 +314,14 @@ impl ConfigureOtp for Admin { raw_data.use_enter, raw_data.use_token_id, raw_data.token_id.as_ptr(), - temp_password_ptr, + self.temp_password_ptr(), ) }) } fn write_totp_slot(&self, data: OtpSlotData, time_window: u16) -> Result<(), Error> { - self.write_otp_slot(data, |raw_data: RawOtpSlotData, temp_password_ptr| unsafe { + let raw_data = RawOtpSlotData::new(data)?; + get_command_result(unsafe { nitrokey_sys::NK_write_totp_slot( raw_data.number, raw_data.name.as_ptr(), @@ -344,19 +331,21 @@ impl ConfigureOtp for Admin { raw_data.use_enter, raw_data.use_token_id, raw_data.token_id.as_ptr(), - temp_password_ptr, + self.temp_password_ptr(), ) }) } fn erase_hotp_slot(&self, slot: u8) -> Result<(), Error> { - let temp_password_ptr = self.temp_password_ptr(); - unsafe { get_command_result(nitrokey_sys::NK_erase_hotp_slot(slot, temp_password_ptr)) } + get_command_result(unsafe { + nitrokey_sys::NK_erase_hotp_slot(slot, self.temp_password_ptr()) + }) } fn erase_totp_slot(&self, slot: u8) -> Result<(), Error> { - let temp_password_ptr = self.temp_password_ptr(); - unsafe { get_command_result(nitrokey_sys::NK_erase_totp_slot(slot, temp_password_ptr)) } + get_command_result(unsafe { + nitrokey_sys::NK_erase_totp_slot(slot, self.temp_password_ptr()) + }) } } diff --git a/src/config.rs b/src/config.rs index 6aa6d10..329f7a6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -30,21 +30,21 @@ pub struct RawConfig { fn config_otp_slot_to_option(value: u8) -> Option { if value < 3 { - return Some(value); + Some(value) + } else { + None } - None } fn option_to_config_otp_slot(value: Option) -> Result { - match value { - Some(value) => { - if value < 3 { - Ok(value) - } else { - Err(LibraryError::InvalidSlot.into()) - } + if let Some(value) = value { + if value < 3 { + Ok(value) + } else { + Err(LibraryError::InvalidSlot.into()) } - None => Ok(255), + } else { + Ok(255) } } diff --git a/src/device.rs b/src/device.rs index 40d6ba4..287268b 100644 --- a/src/device.rs +++ b/src/device.rs @@ -22,14 +22,10 @@ pub enum Model { impl fmt::Display for Model { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match *self { - Model::Pro => "Pro", - Model::Storage => "Storage", - } - ) + f.write_str(match *self { + Model::Pro => "Pro", + Model::Storage => "Storage", + }) } } @@ -44,10 +40,10 @@ pub enum VolumeMode { impl fmt::Display for VolumeMode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - VolumeMode::ReadOnly => f.write_str("read-only"), - VolumeMode::ReadWrite => f.write_str("read-write"), - } + f.write_str(match *self { + VolumeMode::ReadOnly => "read-only", + VolumeMode::ReadWrite => "read-write", + }) } } @@ -330,7 +326,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # } /// ``` fn get_serial_number(&self) -> Result { - unsafe { result_from_string(nitrokey_sys::NK_device_serial_number()) } + result_from_string(unsafe { nitrokey_sys::NK_device_serial_number() }) } /// Returns the number of remaining authentication attempts for the user. The total number of @@ -435,16 +431,14 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # } /// ``` fn get_config(&self) -> Result { - unsafe { - let config_ptr = nitrokey_sys::NK_read_config(); - if config_ptr.is_null() { - return Err(get_last_error()); - } - let config_array_ptr = config_ptr as *const [u8; 5]; - let raw_config = RawConfig::from(*config_array_ptr); - libc::free(config_ptr as *mut libc::c_void); - return Ok(raw_config.into()); + let config_ptr = unsafe { nitrokey_sys::NK_read_config() }; + if config_ptr.is_null() { + return Err(get_last_error()); } + let config_array_ptr = config_ptr as *const [u8; 5]; + let raw_config = unsafe { RawConfig::from(*config_array_ptr) }; + unsafe { libc::free(config_ptr as *mut libc::c_void) }; + Ok(raw_config.into()) } /// Changes the administrator PIN. @@ -475,12 +469,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { fn change_admin_pin(&self, current: &str, new: &str) -> Result<(), Error> { let current_string = get_cstring(current)?; let new_string = get_cstring(new)?; - unsafe { - get_command_result(nitrokey_sys::NK_change_admin_PIN( - current_string.as_ptr(), - new_string.as_ptr(), - )) - } + get_command_result(unsafe { + nitrokey_sys::NK_change_admin_PIN(current_string.as_ptr(), new_string.as_ptr()) + }) } /// Changes the user PIN. @@ -511,12 +502,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { fn change_user_pin(&self, current: &str, new: &str) -> Result<(), Error> { let current_string = get_cstring(current)?; let new_string = get_cstring(new)?; - unsafe { - get_command_result(nitrokey_sys::NK_change_user_PIN( - current_string.as_ptr(), - new_string.as_ptr(), - )) - } + get_command_result(unsafe { + nitrokey_sys::NK_change_user_PIN(current_string.as_ptr(), new_string.as_ptr()) + }) } /// Unlocks the user PIN after three failed login attempts and sets it to the given value. @@ -547,12 +535,12 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { fn unlock_user_pin(&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)?; - unsafe { - get_command_result(nitrokey_sys::NK_unlock_user_password( + get_command_result(unsafe { + nitrokey_sys::NK_unlock_user_password( admin_pin_string.as_ptr(), user_pin_string.as_ptr(), - )) - } + ) + }) } /// Locks the Nitrokey device. @@ -576,7 +564,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # } /// ``` fn lock(&self) -> Result<(), Error> { - unsafe { get_command_result(nitrokey_sys::NK_lock_device()) } + get_command_result(unsafe { nitrokey_sys::NK_lock_device() }) } /// Performs a factory reset on the Nitrokey device. @@ -610,7 +598,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// [`build_aes_key`]: #method.build_aes_key fn factory_reset(&self, admin_pin: &str) -> Result<(), Error> { let admin_pin_string = get_cstring(admin_pin)?; - unsafe { get_command_result(nitrokey_sys::NK_factory_reset(admin_pin_string.as_ptr())) } + get_command_result(unsafe { nitrokey_sys::NK_factory_reset(admin_pin_string.as_ptr()) }) } /// Builds a new AES key on the Nitrokey. @@ -644,7 +632,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// [`factory_reset`]: #method.factory_reset fn build_aes_key(&self, admin_pin: &str) -> Result<(), Error> { let admin_pin_string = get_cstring(admin_pin)?; - unsafe { get_command_result(nitrokey_sys::NK_build_aes_key(admin_pin_string.as_ptr())) } + get_command_result(unsafe { nitrokey_sys::NK_build_aes_key(admin_pin_string.as_ptr()) }) } } @@ -670,14 +658,13 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected pub fn connect() -> Result { - unsafe { - match nitrokey_sys::NK_login_auto() { - 1 => match get_connected_device() { - Some(wrapper) => Ok(wrapper), - None => Err(CommunicationError::NotConnected.into()), - }, - _ => Err(CommunicationError::NotConnected.into()), + if unsafe { nitrokey_sys::NK_login_auto() } == 1 { + match get_connected_device() { + Some(wrapper) => Ok(wrapper), + None => Err(CommunicationError::NotConnected.into()), } + } else { + Err(CommunicationError::NotConnected.into()) } } @@ -711,12 +698,10 @@ pub fn connect_model(model: Model) -> Result { } fn get_connected_model() -> Option { - unsafe { - match nitrokey_sys::NK_get_device_model() { - nitrokey_sys::NK_device_model_NK_PRO => Some(Model::Pro), - nitrokey_sys::NK_device_model_NK_STORAGE => Some(Model::Storage), - _ => None, - } + match unsafe { nitrokey_sys::NK_get_device_model() } { + nitrokey_sys::NK_device_model_NK_PRO => Some(Model::Pro), + nitrokey_sys::NK_device_model_NK_STORAGE => Some(Model::Storage), + _ => None, } } @@ -889,12 +874,9 @@ impl Storage { pub fn change_update_pin(&self, current: &str, new: &str) -> Result<(), Error> { let current_string = get_cstring(current)?; let new_string = get_cstring(new)?; - unsafe { - get_command_result(nitrokey_sys::NK_change_update_password( - current_string.as_ptr(), - new_string.as_ptr(), - )) - } + get_command_result(unsafe { + nitrokey_sys::NK_change_update_password(current_string.as_ptr(), new_string.as_ptr()) + }) } /// Enables the firmware update mode. @@ -928,11 +910,9 @@ impl Storage { /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword pub fn enable_firmware_update(&self, update_pin: &str) -> Result<(), Error> { let update_pin_string = get_cstring(update_pin)?; - unsafe { - get_command_result(nitrokey_sys::NK_enable_firmware_update( - update_pin_string.as_ptr(), - )) - } + get_command_result(unsafe { + nitrokey_sys::NK_enable_firmware_update(update_pin_string.as_ptr()) + }) } /// Enables the encrypted storage volume. @@ -964,7 +944,7 @@ impl Storage { /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword pub fn enable_encrypted_volume(&self, user_pin: &str) -> Result<(), Error> { let user_pin = get_cstring(user_pin)?; - unsafe { get_command_result(nitrokey_sys::NK_unlock_encrypted_volume(user_pin.as_ptr())) } + get_command_result(unsafe { nitrokey_sys::NK_unlock_encrypted_volume(user_pin.as_ptr()) }) } /// Disables the encrypted storage volume. @@ -998,7 +978,7 @@ impl Storage { /// # } /// ``` pub fn disable_encrypted_volume(&self) -> Result<(), Error> { - unsafe { get_command_result(nitrokey_sys::NK_lock_encrypted_volume()) } + get_command_result(unsafe { nitrokey_sys::NK_lock_encrypted_volume() }) } /// Enables a hidden storage volume. @@ -1041,11 +1021,9 @@ impl Storage { /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString pub fn enable_hidden_volume(&self, volume_password: &str) -> Result<(), Error> { let volume_password = get_cstring(volume_password)?; - unsafe { - get_command_result(nitrokey_sys::NK_unlock_hidden_volume( - volume_password.as_ptr(), - )) - } + get_command_result(unsafe { + nitrokey_sys::NK_unlock_hidden_volume(volume_password.as_ptr()) + }) } /// Disables a hidden storage volume. @@ -1080,7 +1058,7 @@ impl Storage { /// # } /// ``` pub fn disable_hidden_volume(&self) -> Result<(), Error> { - unsafe { get_command_result(nitrokey_sys::NK_lock_hidden_volume()) } + get_command_result(unsafe { nitrokey_sys::NK_lock_hidden_volume() }) } /// Creates a hidden volume. @@ -1125,14 +1103,9 @@ impl Storage { password: &str, ) -> Result<(), Error> { let password = get_cstring(password)?; - unsafe { - get_command_result(nitrokey_sys::NK_create_hidden_volume( - slot, - start, - end, - password.as_ptr(), - )) - } + get_command_result(unsafe { + nitrokey_sys::NK_create_hidden_volume(slot, start, end, password.as_ptr()) + }) } /// Sets the access mode of the unencrypted volume. @@ -1221,8 +1194,7 @@ impl Storage { stick_initialized: false, }; let raw_result = unsafe { nitrokey_sys::NK_get_status_storage(&mut raw_status) }; - let result = get_command_result(raw_result); - result.and(Ok(StorageStatus::from(raw_status))) + get_command_result(raw_result).map(|_| StorageStatus::from(raw_status)) } /// Returns the production information for the connected storage device. @@ -1263,8 +1235,7 @@ impl Storage { SD_Card_Manufacturer_u8: 0, }; let raw_result = unsafe { nitrokey_sys::NK_get_storage_production_info(&mut raw_data) }; - let result = get_command_result(raw_result); - result.and(Ok(StorageProductionInfo::from(raw_data))) + get_command_result(raw_result).map(|_| StorageProductionInfo::from(raw_data)) } /// Clears the warning for a new SD card. diff --git a/src/otp.rs b/src/otp.rs index 7535a77..430b127 100644 --- a/src/otp.rs +++ b/src/otp.rs @@ -219,7 +219,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 { - unsafe { result_from_string(nitrokey_sys::NK_get_hotp_slot_name(slot)) } + result_from_string(unsafe { nitrokey_sys::NK_get_hotp_slot_name(slot) }) } /// Returns the name of the given TOTP slot. @@ -248,7 +248,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 { - unsafe { result_from_string(nitrokey_sys::NK_get_totp_slot_name(slot)) } + result_from_string(unsafe { nitrokey_sys::NK_get_totp_slot_name(slot) }) } /// Generates an HOTP code on the given slot. This operation may require user authorization, @@ -279,9 +279,7 @@ pub trait GenerateOtp { /// [`NotAuthorized`]: enum.CommandError.html#variant.NotAuthorized /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed fn get_hotp_code(&self, slot: u8) -> Result { - unsafe { - return result_from_string(nitrokey_sys::NK_get_hotp_code(slot)); - } + result_from_string(unsafe { nitrokey_sys::NK_get_hotp_code(slot) }) } /// Generates a TOTP code on the given slot. This operation may require user authorization, @@ -324,9 +322,7 @@ pub trait GenerateOtp { /// [`NotAuthorized`]: enum.CommandError.html#variant.NotAuthorized /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed fn get_totp_code(&self, slot: u8) -> Result { - unsafe { - return result_from_string(nitrokey_sys::NK_get_totp_code(slot, 0, 0, 0)); - } + result_from_string(unsafe { nitrokey_sys::NK_get_totp_code(slot, 0, 0, 0) }) } } diff --git a/src/pws.rs b/src/pws.rs index a21527c..c89b73f 100644 --- a/src/pws.rs +++ b/src/pws.rs @@ -121,12 +121,8 @@ fn get_password_safe<'a>( user_pin: &str, ) -> Result, Error> { let user_pin_string = get_cstring(user_pin)?; - let result = unsafe { - get_command_result(nitrokey_sys::NK_enable_password_safe( - user_pin_string.as_ptr(), - )) - }; - result.map(|()| PasswordSafe { _device: device }) + get_command_result(unsafe { nitrokey_sys::NK_enable_password_safe(user_pin_string.as_ptr()) }) + .map(|_| PasswordSafe { _device: device }) } fn get_pws_result(s: String) -> Result { @@ -211,7 +207,7 @@ impl<'a> PasswordSafe<'a> { /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed pub fn get_slot_name(&self, slot: u8) -> Result { - unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_name(slot)) } + result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_name(slot) }) .and_then(get_pws_result) } @@ -244,7 +240,7 @@ impl<'a> PasswordSafe<'a> { /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed pub fn get_slot_login(&self, slot: u8) -> Result { - unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_login(slot)) } + result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_login(slot) }) .and_then(get_pws_result) } @@ -277,7 +273,7 @@ impl<'a> PasswordSafe<'a> { /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed pub fn get_slot_password(&self, slot: u8) -> Result { - unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_password(slot)) } + result_from_string(unsafe { nitrokey_sys::NK_get_password_safe_slot_password(slot) }) .and_then(get_pws_result) } @@ -317,14 +313,14 @@ impl<'a> PasswordSafe<'a> { let name_string = get_cstring(name)?; let login_string = get_cstring(login)?; let password_string = get_cstring(password)?; - unsafe { - get_command_result(nitrokey_sys::NK_write_password_safe_slot( + get_command_result(unsafe { + nitrokey_sys::NK_write_password_safe_slot( slot, name_string.as_ptr(), login_string.as_ptr(), password_string.as_ptr(), - )) - } + ) + }) } /// Erases the given slot. Erasing clears the stored name, login and password (if the slot was @@ -353,7 +349,7 @@ impl<'a> PasswordSafe<'a> { /// /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot pub fn erase_slot(&self, slot: u8) -> Result<(), Error> { - unsafe { get_command_result(nitrokey_sys::NK_erase_password_safe_slot(slot)) } + get_command_result(unsafe { nitrokey_sys::NK_erase_password_safe_slot(slot) }) } } diff --git a/src/util.rs b/src/util.rs index 2542a7b..f8ad9c9 100644 --- a/src/util.rs +++ b/src/util.rs @@ -29,32 +29,31 @@ pub enum LogLevel { } pub fn owned_str_from_ptr(ptr: *const c_char) -> String { - unsafe { - return CStr::from_ptr(ptr).to_string_lossy().into_owned(); - } + unsafe { CStr::from_ptr(ptr) } + .to_string_lossy() + .into_owned() } pub fn result_from_string(ptr: *const c_char) -> Result { if ptr.is_null() { return Err(Error::UnexpectedError); } - unsafe { - let s = owned_str_from_ptr(ptr); - free(ptr as *mut c_void); - // 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) - } else { - Ok(s) - } + let s = owned_str_from_ptr(ptr); + unsafe { free(ptr as *mut c_void) }; + // 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) + } else { + Ok(s) } } pub fn get_command_result(value: c_int) -> Result<(), Error> { - match value { - 0 => Ok(()), - other => Err(Error::from(other)), + if value == 0 { + Ok(()) + } else { + Err(Error::from(value)) } } @@ -63,10 +62,10 @@ pub fn get_last_result() -> Result<(), Error> { } pub fn get_last_error() -> Error { - return match get_last_result() { + match get_last_result() { Ok(()) => Error::UnexpectedError, Err(err) => err, - }; + } } pub fn generate_password(length: usize) -> Result, Error> { -- cgit v1.2.3 From 425010284341fcc745072dcd39b9fa398ae8db69 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 23 Jan 2019 04:02:51 +0000 Subject: Add Pro::new and Storage::new functions --- CHANGELOG.md | 1 + src/device.rs | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 365b864..92a4dac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ instantiation impossible. - Refactor and clean up internal code: - Prefer using the `Into` trait over numeric casting. + - Add `Pro::new` and `Storage::new` functions. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/device.rs b/src/device.rs index 287268b..2abf801 100644 --- a/src/device.rs +++ b/src/device.rs @@ -788,12 +788,16 @@ impl Pro { pub fn connect() -> Result { // TODO: maybe Option instead of Result? match connect_enum(Model::Pro) { - true => Ok(Pro { - marker: marker::PhantomData, - }), + true => Ok(Pro::new()), false => Err(CommunicationError::NotConnected.into()), } } + + fn new() -> Pro { + Pro { + marker: marker::PhantomData, + } + } } impl Drop for Pro { @@ -836,13 +840,17 @@ impl Storage { pub fn connect() -> Result { // TODO: maybe Option instead of Result? match connect_enum(Model::Storage) { - true => Ok(Storage { - marker: marker::PhantomData, - }), + true => Ok(Storage::new()), false => Err(CommunicationError::NotConnected.into()), } } + fn new() -> Storage { + Storage { + marker: marker::PhantomData, + } + } + /// Changes the update PIN. /// /// The update PIN is used to enable firmware updates. Unlike the user and the admin PIN, the -- cgit v1.2.3 From 5ef83ad507d1e5f51152b20628314936b4fb833c Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 23 Jan 2019 04:09:26 +0000 Subject: Implement From and From for DeviceWrapper --- CHANGELOG.md | 1 + src/device.rs | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 92a4dac..6305ebe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Refactor and clean up internal code: - Prefer using the `Into` trait over numeric casting. - Add `Pro::new` and `Storage::new` functions. +- Implement `From` and `From` for `DeviceWrapper`. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/device.rs b/src/device.rs index 2abf801..ad75a44 100644 --- a/src/device.rs +++ b/src/device.rs @@ -707,12 +707,8 @@ fn get_connected_model() -> Option { fn create_device_wrapper(model: Model) -> DeviceWrapper { match model { - Model::Pro => DeviceWrapper::Pro(Pro { - marker: marker::PhantomData, - }), - Model::Storage => DeviceWrapper::Storage(Storage { - marker: marker::PhantomData, - }), + Model::Pro => Pro::new().into(), + Model::Storage => Storage::new().into(), } } @@ -737,6 +733,18 @@ impl DeviceWrapper { } } +impl From for DeviceWrapper { + fn from(device: Pro) -> Self { + DeviceWrapper::Pro(device) + } +} + +impl From for DeviceWrapper { + fn from(device: Storage) -> Self { + DeviceWrapper::Storage(device) + } +} + impl GenerateOtp for DeviceWrapper { fn get_hotp_slot_name(&self, slot: u8) -> Result { self.device().get_hotp_slot_name(slot) -- cgit v1.2.3 From c79ddf8116659efd1aa7de42bb85337632f238dd Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 23 Jan 2019 04:16:59 +0000 Subject: Add Error::Utf8Error variant Previously, we just ignored UTF-8 errors. This patch prepares the Utf8Error variant so that we are able to return UTF-8 errors. --- CHANGELOG.md | 1 + src/error.rs | 11 +++++++++++ 2 files changed, 12 insertions(+) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 6305ebe..c800521 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Prefer using the `Into` trait over numeric casting. - Add `Pro::new` and `Storage::new` functions. - Implement `From` and `From` for `DeviceWrapper`. +- Add `Error::Utf8Error` variant. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/error.rs b/src/error.rs index cde9a34..4b82c6e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,6 +2,7 @@ use std::error; use std::fmt; use std::os::raw; use std::result; +use std::str; /// An error returned by the nitrokey crate. #[derive(Debug)] @@ -18,6 +19,8 @@ pub enum Error { UnexpectedError, /// An unknown error returned by libnitrokey. Unknown(i64), + /// An error occurred when interpreting a UTF-8 string. + Utf8Error(str::Utf8Error), } impl From for Error { @@ -58,6 +61,12 @@ impl From for Error { } } +impl From for Error { + fn from(error: str::Utf8Error) -> Self { + Error::Utf8Error(error) + } +} + impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { @@ -67,6 +76,7 @@ impl error::Error for Error { Error::RandError(ref err) => Some(err), Error::UnexpectedError => None, Error::Unknown(_) => None, + Error::Utf8Error(ref err) => Some(err), } } } @@ -80,6 +90,7 @@ impl fmt::Display for Error { Error::RandError(ref err) => write!(f, "RNG error: {}", err), Error::UnexpectedError => write!(f, "An unexpected error occurred"), Error::Unknown(ref err) => write!(f, "Unknown error: {}", err), + Error::Utf8Error(ref err) => write!(f, "UTF-8 error: {}", err), } } } -- cgit v1.2.3 From d4663961c41a0fb6f81f4a54aefd0fedce49d350 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 23 Jan 2019 04:27:14 +0000 Subject: Return UTF-8 error if libnitrokey returns an invalid string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, we used lossy UTF-8 conversion. Yet the user should be notified if we have a problem instead of silently changing the data. Therefore, we now return an error if we enocunter an invalid UTF-8 string. This leads to a change in `get_library_version`’s signature. --- CHANGELOG.md | 2 ++ src/lib.rs | 17 +++++++++++++---- src/util.rs | 9 +++++---- tests/lib.rs | 2 +- 4 files changed, 21 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index c800521..70bd7cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ - Add `Pro::new` and `Storage::new` functions. - Implement `From` and `From` for `DeviceWrapper`. - Add `Error::Utf8Error` variant. + - Return `Result` instead of `Version` from `get_library_version`. + - Return `Error::Utf8Error` if libnitrokey returns an invalid UTF-8 string. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/lib.rs b/src/lib.rs index 993ec92..a1edb6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -168,21 +168,30 @@ pub fn set_log_level(level: LogLevel) { /// Returns the libnitrokey library version. /// +/// # Errors +/// +/// - [`Utf8Error`][] if libnitrokey returned an invalid UTF-8 string +/// /// # Example /// /// ``` -/// let version = nitrokey::get_library_version(); +/// # fn main() -> Result<(), nitrokey::Error> { +/// let version = nitrokey::get_library_version()?; /// println!("Using libnitrokey {}", version.git); +/// # Ok(()) +/// # } /// ``` -pub fn get_library_version() -> Version { +/// +/// [`Utf8Error`]: enum.Error.html#variant.Utf8Error +pub fn get_library_version() -> Result { // 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) + 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() }; - Version { git, major, minor } + Ok(Version { git, major, minor }) } diff --git a/src/util.rs b/src/util.rs index f8ad9c9..64dde39 100644 --- a/src/util.rs +++ b/src/util.rs @@ -28,17 +28,18 @@ pub enum LogLevel { DebugL2, } -pub fn owned_str_from_ptr(ptr: *const c_char) -> String { +pub fn owned_str_from_ptr(ptr: *const c_char) -> Result { unsafe { CStr::from_ptr(ptr) } - .to_string_lossy() - .into_owned() + .to_str() + .map(String::from) + .map_err(Error::from) } pub fn result_from_string(ptr: *const c_char) -> Result { if ptr.is_null() { return Err(Error::UnexpectedError); } - let s = owned_str_from_ptr(ptr); + let s = owned_str_from_ptr(ptr)?; unsafe { free(ptr as *mut c_void) }; // 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. diff --git a/tests/lib.rs b/tests/lib.rs index c92e224..d298048 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,6 +1,6 @@ #[test] fn get_library_version() { - let version = nitrokey::get_library_version(); + let version = nitrokey::get_library_version().unwrap(); assert!(version.git.is_empty() || version.git.starts_with("v")); assert!(version.major > 0); -- cgit v1.2.3 From b00bbaa5603504597729ed2ce0d1e8ff50ea078d Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 23 Jan 2019 04:36:57 +0000 Subject: Implement From<(T: Device, Error)> for Error Not all users of the authenticate methods want to use the device after an error, so implementing From<(T: Device, Error)> for Error makes it easier for them to discard the device. --- CHANGELOG.md | 1 + TODO.md | 1 - src/error.rs | 8 ++++++++ 3 files changed, 9 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 70bd7cb..c28c228 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - Add `Error::Utf8Error` variant. - Return `Result` instead of `Version` from `get_library_version`. - Return `Error::Utf8Error` if libnitrokey returns an invalid UTF-8 string. +- Implement `From<(T: Device, Error)>` for `Error`. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/TODO.md b/TODO.md index 0467f1d..49e4e08 100644 --- a/TODO.md +++ b/TODO.md @@ -8,7 +8,6 @@ - Fix timing issues with the `totp_no_pin` and `totp_pin` test cases. - Clear passwords from memory. - Find a nicer syntax for the `write_config` test. -- Consider implementing `Into` for `(Device, CommandError)` - Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware issue 65][]). - Disable creation of multiple password safes at the same time. diff --git a/src/error.rs b/src/error.rs index 4b82c6e..551dd0f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,6 +4,8 @@ use std::os::raw; use std::result; use std::str; +use crate::device; + /// An error returned by the nitrokey crate. #[derive(Debug)] pub enum Error { @@ -67,6 +69,12 @@ impl From for Error { } } +impl From<(T, Error)> for Error { + fn from((_, err): (T, Error)) -> Self { + err + } +} + impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { -- cgit v1.2.3 From fdb7bac3063e62776bfc13f184cf786da19f42d1 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 23 Jan 2019 16:33:26 +0100 Subject: Add license and copyright information This patch adds license and copyright information to all files to make nitrokey-rs compliant with the REUSE practices [0]. [0] https://reuse.software/practices/2.0/ --- .builds/archlinux-use-system-lib.yaml | 2 ++ .builds/archlinux.yml | 2 ++ CHANGELOG.md | 5 +++++ Cargo.toml | 3 +++ LICENSE | 3 +++ README.md | 8 ++++++++ TODO.md | 5 +++++ src/auth.rs | 3 +++ src/config.rs | 3 +++ src/device.rs | 3 +++ src/error.rs | 3 +++ src/lib.rs | 3 +++ src/otp.rs | 3 +++ src/pws.rs | 3 +++ src/util.rs | 3 +++ tests/device.rs | 3 +++ tests/lib.rs | 3 +++ tests/otp.rs | 3 +++ tests/pws.rs | 3 +++ tests/util/mod.rs | 3 +++ 20 files changed, 67 insertions(+) (limited to 'src') diff --git a/.builds/archlinux-use-system-lib.yaml b/.builds/archlinux-use-system-lib.yaml index 6fba33a..13f7581 100644 --- a/.builds/archlinux-use-system-lib.yaml +++ b/.builds/archlinux-use-system-lib.yaml @@ -1,3 +1,5 @@ +# Copyright (C) 2019 Robin Krahl +# SPDX-License-Identifier: MIT image: archlinux packages: - rust diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml index 9d45386..7dab954 100644 --- a/.builds/archlinux.yml +++ b/.builds/archlinux.yml @@ -1,3 +1,5 @@ +# Copyright (C) 2019 Robin Krahl +# SPDX-License-Identifier: MIT image: archlinux packages: - rust diff --git a/CHANGELOG.md b/CHANGELOG.md index c28c228..49ff8fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ + + # Unreleased - Remove the `test-pro` and `test-storage` features. - Implement `Display` for `Version`. diff --git a/Cargo.toml b/Cargo.toml index ceaa57d..696adfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,6 @@ +# Copyright (C) 2019 Robin Krahl +# SPDX-License-Identifier: MIT + [package] name = "nitrokey" version = "0.3.4" diff --git a/LICENSE b/LICENSE index 1a3601d..6c67cd5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,6 @@ +Valid-License-Identifier: MIT +License-Text: + The MIT License (MIT) Copyright (c) 2018 Robin Krahl diff --git a/README.md b/README.md index 0819c9d..8e1d98e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ + + # nitrokey-rs A libnitrokey wrapper for Rust providing access to Nitrokey devices. @@ -73,6 +78,8 @@ mail to [nitrokey-rs-dev@ireas.org][]. This project is licensed under the [MIT License][]. `libnitrokey` is licensed under the [LGPL-3.0][]. +`nitrokey-rs` complies with [version 2.0 of the REUSE practices][reuse]. + [Documentation]: https://docs.rs/nitrokey [Nitrokey udev rules]: https://www.nitrokey.com/documentation/frequently-asked-questions-faq#openpgp-card-not-available [`libnitrokey`]: https://github.com/nitrokey/libnitrokey @@ -81,3 +88,4 @@ under the [LGPL-3.0][]. [pull request #114]: https://github.com/Nitrokey/libnitrokey/pull/114 [MIT license]: https://opensource.org/licenses/MIT [LGPL-3.0]: https://opensource.org/licenses/lgpl-3.0.html +[reuse]: https://reuse.software/practices/2.0/ diff --git a/TODO.md b/TODO.md index d3f0018..db45bb5 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,8 @@ + + - Add support for the currently unsupported commands: - `NK_send_startup` - `NK_fill_SD_card_with_random_data` diff --git a/src/auth.rs b/src/auth.rs index b97bee6..18b6572 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2018-2019 Robin Krahl +// SPDX-License-Identifier: MIT + use std::ops::Deref; use std::os::raw::c_char; use std::os::raw::c_int; diff --git a/src/config.rs b/src/config.rs index 329f7a6..c273792 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2018-2019 Robin Krahl +// SPDX-License-Identifier: MIT + use crate::error::{Error, LibraryError}; /// The configuration for a Nitrokey. diff --git a/src/device.rs b/src/device.rs index ad75a44..c4af8a8 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2018-2019 Robin Krahl +// SPDX-License-Identifier: MIT + use std::fmt; use std::marker; diff --git a/src/error.rs b/src/error.rs index 551dd0f..9cdb932 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2019 Robin Krahl +// SPDX-License-Identifier: MIT + use std::error; use std::fmt; use std::os::raw; diff --git a/src/lib.rs b/src/lib.rs index a1edb6b..9d15d03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2018-2019 Robin Krahl +// SPDX-License-Identifier: MIT + //! Provides access to a Nitrokey device using the native libnitrokey API. //! //! # Usage diff --git a/src/otp.rs b/src/otp.rs index 430b127..6e0379b 100644 --- a/src/otp.rs +++ b/src/otp.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2018-2019 Robin Krahl +// SPDX-License-Identifier: MIT + use std::ffi::CString; use nitrokey_sys; diff --git a/src/pws.rs b/src/pws.rs index c89b73f..fcf057b 100644 --- a/src/pws.rs +++ b/src/pws.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2018-2019 Robin Krahl +// SPDX-License-Identifier: MIT + use libc; use nitrokey_sys; diff --git a/src/util.rs b/src/util.rs index 64dde39..5f25655 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2018-2019 Robin Krahl +// SPDX-License-Identifier: MIT + use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_int}; diff --git a/tests/device.rs b/tests/device.rs index 59c9348..c790049 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2018-2019 Robin Krahl +// SPDX-License-Identifier: MIT + mod util; use std::ffi::CStr; diff --git a/tests/lib.rs b/tests/lib.rs index d298048..697024d 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2019 Robin Krahl +// SPDX-License-Identifier: MIT + #[test] fn get_library_version() { let version = nitrokey::get_library_version().unwrap(); diff --git a/tests/otp.rs b/tests/otp.rs index 96da371..fb20768 100644 --- a/tests/otp.rs +++ b/tests/otp.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2018-2019 Robin Krahl +// SPDX-License-Identifier: MIT + mod util; use std::fmt::Debug; diff --git a/tests/pws.rs b/tests/pws.rs index 8bdf532..df99e1c 100644 --- a/tests/pws.rs +++ b/tests/pws.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2018-2019 Robin Krahl +// SPDX-License-Identifier: MIT + mod util; use std::ffi::CStr; diff --git a/tests/util/mod.rs b/tests/util/mod.rs index 4a00a66..49ec13e 100644 --- a/tests/util/mod.rs +++ b/tests/util/mod.rs @@ -1,3 +1,6 @@ +// Copyright (C) 2018-2019 Robin Krahl +// SPDX-License-Identifier: MIT + pub static ADMIN_PASSWORD: &str = "12345678"; pub static USER_PASSWORD: &str = "123456"; -- cgit v1.2.3 From 809d31a4273505487febb2dd281376d2bb3766ab Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Fri, 25 Jan 2019 18:46:57 +0000 Subject: Remove rand_core::Error from public API rand_core does not have a stable release yet, and it is unlikely that there will be one soon. To be able to stabilize nitrokey without waiting for a stable rand_core version, we remove the rand_core::Error type from the public API and replace it with a Box. --- src/error.rs | 10 ++-------- src/util.rs | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/error.rs b/src/error.rs index 9cdb932..1b36975 100644 --- a/src/error.rs +++ b/src/error.rs @@ -19,7 +19,7 @@ pub enum Error { /// A library usage error. LibraryError(LibraryError), /// An error that occured during random number generation. - RandError(rand_core::Error), + RandError(Box), /// An error that is caused by an unexpected value returned by libnitrokey. UnexpectedError, /// An unknown error returned by libnitrokey. @@ -60,12 +60,6 @@ impl From for Error { } } -impl From for Error { - fn from(error: rand_core::Error) -> Self { - Error::RandError(error) - } -} - impl From for Error { fn from(error: str::Utf8Error) -> Self { Error::Utf8Error(error) @@ -84,7 +78,7 @@ impl error::Error for Error { Error::CommandError(ref err) => Some(err), Error::CommunicationError(ref err) => Some(err), Error::LibraryError(ref err) => Some(err), - Error::RandError(ref err) => Some(err), + Error::RandError(ref err) => Some(err.as_ref()), Error::UnexpectedError => None, Error::Unknown(_) => None, Error::Utf8Error(ref err) => Some(err), diff --git a/src/util.rs b/src/util.rs index 5f25655..d87cd7c 100644 --- a/src/util.rs +++ b/src/util.rs @@ -73,7 +73,7 @@ pub fn get_last_error() -> Error { } pub fn generate_password(length: usize) -> Result, Error> { - let mut rng = OsRng::new()?; + let mut rng = OsRng::new().map_err(|err| Error::RandError(Box::new(err)))?; let mut data = vec![0u8; length]; rng.fill_bytes(&mut data[..]); Ok(data) -- cgit v1.2.3 From a30562638aed90d113739bb36dd6814f6cf7ace2 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 27 Jan 2019 13:31:36 +0000 Subject: Remove the Result typedef Many of our functions do not return a Result<_, Error>, but for example a Result<_, (Device, Error)>. We only use the typedef in one function, but it makes the other functions more complicated as we have to use result::Result (if crate::Result is imported). Therefore, this patch removes the typedef. Applications or libraries can still redefine it if they want to. --- CHANGELOG.md | 2 +- src/error.rs | 4 ---- src/lib.rs | 4 ++-- 3 files changed, 3 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index b9983ed..271230d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ SPDX-License-Identifier: MIT - Introduce `DEFAULT_ADMIN_PIN` and `DEFAULT_USER_PIN` constants. - Refactor the error handling code: - Implement `std::error::Error` for `CommandError`. - - Add the `Error` enum and the `Result` typedef. + - Add the `Error` enum. - Add the `LibraryError` enum and move the library error variants from `CommandError` to `LibraryError`. - Add the `CommunicationError` enum and move the communication error variants diff --git a/src/error.rs b/src/error.rs index 1b36975..5c8c52e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,7 +4,6 @@ use std::error; use std::fmt; use std::os::raw; -use std::result; use std::str; use crate::device; @@ -100,9 +99,6 @@ impl fmt::Display for Error { } } -/// A result returned by the nitrokey crate. -pub type Result = result::Result; - /// An error reported by the Nitrokey device in the response packet. #[derive(Clone, Copy, Debug, PartialEq)] pub enum CommandError { diff --git a/src/lib.rs b/src/lib.rs index 9d15d03..18c86e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,7 +107,7 @@ pub use crate::device::{ connect, connect_model, Device, DeviceWrapper, Model, Pro, SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus, }; -pub use crate::error::{CommandError, CommunicationError, Error, LibraryError, Result}; +pub use crate::error::{CommandError, CommunicationError, Error, LibraryError}; pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData}; pub use crate::pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT}; pub use crate::util::LogLevel; @@ -186,7 +186,7 @@ pub fn set_log_level(level: LogLevel) { /// ``` /// /// [`Utf8Error`]: enum.Error.html#variant.Utf8Error -pub fn get_library_version() -> Result { +pub fn get_library_version() -> Result { // 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() { -- cgit v1.2.3 From 33a65c1635e54ae51089ef3c37a749d67853be02 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 27 Jan 2019 13:36:13 +0000 Subject: Rename Error::Unknown to Error::UnknownError For consistency with the other Error variants, we rename Unknown to UnknownError. --- CHANGELOG.md | 2 +- src/error.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 271230d..24c79af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ SPDX-License-Identifier: MIT from `CommandError` to `CommunicationError`. - Return `Error` instead of `CommandError` in all public functions. - Move the `CommandError::RngError` variant to `Error::RandError` and the - `CommandError::Unknown` variant to `Error::Unknown`. + `CommandError::Unknown` variant to `Error::UnknownError`. - Return `CommunicationError::NotConnected` instead of `CommandError::Undefined` from the connect functions. - Remove the `CommandError::Undefined` variant. diff --git a/src/error.rs b/src/error.rs index 5c8c52e..1730171 100644 --- a/src/error.rs +++ b/src/error.rs @@ -22,7 +22,7 @@ pub enum Error { /// An error that is caused by an unexpected value returned by libnitrokey. UnexpectedError, /// An unknown error returned by libnitrokey. - Unknown(i64), + UnknownError(i64), /// An error occurred when interpreting a UTF-8 string. Utf8Error(str::Utf8Error), } @@ -36,7 +36,7 @@ impl From for Error { } else if let Some(err) = LibraryError::try_from(code) { Error::LibraryError(err) } else { - Error::Unknown(code.into()) + Error::UnknownError(code.into()) } } } @@ -79,7 +79,7 @@ impl error::Error for Error { Error::LibraryError(ref err) => Some(err), Error::RandError(ref err) => Some(err.as_ref()), Error::UnexpectedError => None, - Error::Unknown(_) => None, + Error::UnknownError(_) => None, Error::Utf8Error(ref err) => Some(err), } } @@ -93,7 +93,7 @@ impl fmt::Display for Error { Error::LibraryError(ref err) => write!(f, "Library error: {}", err), Error::RandError(ref err) => write!(f, "RNG error: {}", err), Error::UnexpectedError => write!(f, "An unexpected error occurred"), - Error::Unknown(ref err) => write!(f, "Unknown error: {}", err), + Error::UnknownError(ref err) => write!(f, "Unknown error: {}", err), Error::Utf8Error(ref err) => write!(f, "UTF-8 error: {}", err), } } -- cgit v1.2.3 From e311ee31b819092a119fa24930ab5777d3c4fd71 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 27 Jan 2019 13:38:16 +0000 Subject: Remove the static lifetime modifier from constants The DEFAULT_{ADMIN,USER}_PIN constants implicitly have static lifetime. Therefore we can remove the static lifetime modifiers. --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 18c86e2..f2d524e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -113,9 +113,9 @@ pub use crate::pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT}; pub use crate::util::LogLevel; /// The default admin PIN for all Nitrokey devices. -pub const DEFAULT_ADMIN_PIN: &'static str = "12345678"; +pub const DEFAULT_ADMIN_PIN: &str = "12345678"; /// The default user PIN for all Nitrokey devices. -pub const DEFAULT_USER_PIN: &'static str = "123456"; +pub const DEFAULT_USER_PIN: &str = "123456"; /// A version of the libnitrokey library. /// -- cgit v1.2.3 From 3cab3525e9c0bd743aa419d286de38d346776fbd Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 27 Jan 2019 13:42:12 +0000 Subject: Replace or with or_else in get_cstring To avoid unnecessary function calls, we replace the or with an or_else in get_cstring. --- src/util.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/util.rs b/src/util.rs index d87cd7c..b7e8cd3 100644 --- a/src/util.rs +++ b/src/util.rs @@ -80,7 +80,7 @@ pub fn generate_password(length: usize) -> Result, Error> { } pub fn get_cstring>>(s: T) -> Result { - CString::new(s).or(Err(LibraryError::InvalidString.into())) + CString::new(s).or_else(|_| Err(LibraryError::InvalidString.into())) } impl Into for LogLevel { -- cgit v1.2.3 From 41a2303ad06f409cb932cf570ff6cc04dd6692fe Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 27 Jan 2019 14:04:24 +0000 Subject: Use if instead of match for boolean expression --- src/device.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/device.rs b/src/device.rs index c4af8a8..386ce94 100644 --- a/src/device.rs +++ b/src/device.rs @@ -798,9 +798,10 @@ impl Pro { /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected pub fn connect() -> Result { // TODO: maybe Option instead of Result? - match connect_enum(Model::Pro) { - true => Ok(Pro::new()), - false => Err(CommunicationError::NotConnected.into()), + if connect_enum(Model::Pro) { + Ok(Pro::new()) + } else { + Err(CommunicationError::NotConnected.into()) } } @@ -850,9 +851,10 @@ impl Storage { /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected pub fn connect() -> Result { // TODO: maybe Option instead of Result? - match connect_enum(Model::Storage) { - true => Ok(Storage::new()), - false => Err(CommunicationError::NotConnected.into()), + if connect_enum(Model::Storage) { + Ok(Storage::new()) + } else { + Err(CommunicationError::NotConnected.into()) } } -- cgit v1.2.3 From c30cbd35ba187cd6e5055d3beb8420b11fb030ec Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 27 Jan 2019 23:23:00 +0000 Subject: Always return a Result when communicating with a device Previously, we sometimes returned a value without wrapping it in a result if the API method did not indicate errors in the return value. But we can detect errors using the NK_get_last_command_status function. This patch changes the return types of these methods to Result<_, Error> and adds error checks. --- CHANGELOG.md | 1 + src/device.rs | 39 +++++++++++++++++++++++---------------- src/util.rs | 4 ++++ tests/device.rs | 9 +++++---- 4 files changed, 33 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 24c79af..25a8c31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ SPDX-License-Identifier: MIT - Return `Error::Utf8Error` if libnitrokey returns an invalid UTF-8 string. - Implement `From<(T: Device, Error)>` for `Error`. - Fix timing issues with the `totp_no_pin` and `totp_pin` test cases. +- Always return a `Result` in functions that communicate with a device. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/device.rs b/src/device.rs index 386ce94..4178922 100644 --- a/src/device.rs +++ b/src/device.rs @@ -12,7 +12,9 @@ use crate::config::{Config, RawConfig}; use crate::error::{CommunicationError, Error}; use crate::otp::GenerateOtp; use crate::pws::GetPasswordSafe; -use crate::util::{get_command_result, get_cstring, get_last_error, result_from_string}; +use crate::util::{ + get_command_result, get_cstring, get_last_error, result_from_string, result_or_error, +}; /// Available Nitrokey models. #[derive(Clone, Copy, Debug, PartialEq)] @@ -343,13 +345,15 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; - /// let count = device.get_user_retry_count(); - /// println!("{} remaining authentication attempts (user)", count); + /// match device.get_user_retry_count() { + /// Ok(count) => println!("{} remaining authentication attempts (user)", count), + /// Err(err) => println!("Could not get user retry count: {}", err), + /// } /// # Ok(()) /// # } /// ``` - fn get_user_retry_count(&self) -> u8 { - unsafe { nitrokey_sys::NK_get_user_retry_count() } + fn get_user_retry_count(&self) -> Result { + result_or_error(unsafe { nitrokey_sys::NK_get_user_retry_count() }) } /// Returns the number of remaining authentication attempts for the admin. The total number of @@ -364,12 +368,15 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// let count = device.get_admin_retry_count(); - /// println!("{} remaining authentication attempts (admin)", count); + /// match device.get_admin_retry_count() { + /// Ok(count) => println!("{} remaining authentication attempts (admin)", count), + /// Err(err) => println!("Could not get admin retry count: {}", err), + /// } /// # Ok(()) /// # } /// ``` - fn get_admin_retry_count(&self) -> u8 { - unsafe { nitrokey_sys::NK_get_admin_retry_count() } + fn get_admin_retry_count(&self) -> Result { + result_or_error(unsafe { nitrokey_sys::NK_get_admin_retry_count() }) } /// Returns the major part of the firmware version (should be zero). @@ -384,14 +391,14 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// let device = nitrokey::connect()?; /// println!( /// "Firmware version: {}.{}", - /// device.get_major_firmware_version(), - /// device.get_minor_firmware_version(), + /// device.get_major_firmware_version().unwrap(), + /// device.get_minor_firmware_version().unwrap(), /// ); /// # Ok(()) /// # } /// ``` - fn get_major_firmware_version(&self) -> i32 { - unsafe { nitrokey_sys::NK_get_major_firmware_version() } + fn get_major_firmware_version(&self) -> Result { + result_or_error(unsafe { nitrokey_sys::NK_get_major_firmware_version() }) } /// Returns the minor part of the firmware version (for example 8 for version 0.8). @@ -406,13 +413,13 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// let device = nitrokey::connect()?; /// println!( /// "Firmware version: {}.{}", - /// device.get_major_firmware_version(), - /// device.get_minor_firmware_version(), + /// device.get_major_firmware_version().unwrap(), + /// device.get_minor_firmware_version().unwrap(), /// ); /// # Ok(()) /// # } - fn get_minor_firmware_version(&self) -> i32 { - unsafe { nitrokey_sys::NK_get_minor_firmware_version() } + fn get_minor_firmware_version(&self) -> Result { + result_or_error(unsafe { nitrokey_sys::NK_get_minor_firmware_version() }) } /// Returns the current configuration of the Nitrokey device. diff --git a/src/util.rs b/src/util.rs index b7e8cd3..fdb73c3 100644 --- a/src/util.rs +++ b/src/util.rs @@ -53,6 +53,10 @@ pub fn result_from_string(ptr: *const c_char) -> Result { } } +pub fn result_or_error(value: T) -> Result { + get_last_result().and(Ok(value)) +} + pub fn get_command_result(value: c_int) -> Result<(), Error> { if value == 0 { Ok(()) diff --git a/tests/device.rs b/tests/device.rs index c790049..7ab4d66 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -95,9 +95,10 @@ fn get_serial_number(device: DeviceWrapper) { } #[test_device] fn get_firmware_version(device: Pro) { - assert_eq!(0, device.get_major_firmware_version()); + assert_ok!(0, device.get_major_firmware_version()); let minor = device.get_minor_firmware_version(); - assert!(minor > 0); + assert!(minor.is_ok()); + assert!(minor.unwrap() > 0); } fn admin_retry(device: T, suffix: &str, count: u8) -> T { @@ -106,7 +107,7 @@ fn admin_retry(device: T, suffix: &str, count: u8) -> Ok(admin) => admin.device(), Err((device, _)) => device, }; - assert_eq!(count, device.get_admin_retry_count()); + assert_ok!(count, device.get_admin_retry_count()); return device; } @@ -116,7 +117,7 @@ fn user_retry(device: T, suffix: &str, count: u8) -> T Ok(admin) => admin.device(), Err((device, _)) => device, }; - assert_eq!(count, device.get_user_retry_count()); + assert_ok!(count, device.get_user_retry_count()); return device; } -- cgit v1.2.3 From 1d68e24db4078ad1a004afd7bec90a81e7d31ec8 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 27 Jan 2019 23:34:04 +0000 Subject: Add get_firmware_version method This patch combines the get_{major,minor}_firmware_version methods into the new get_firmware_version method that returns a FirmwareVersion struct. Currently, this requires casting from i32 to u8. But this will be fixed with the next libnitrokey version as we change the return types for the firmware getters. --- CHANGELOG.md | 1 + src/device.rs | 45 ++++++++++++++++----------------------------- tests/device.rs | 7 +++---- 3 files changed, 20 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 25a8c31..c51727e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ SPDX-License-Identifier: MIT - Implement `From<(T: Device, Error)>` for `Error`. - Fix timing issues with the `totp_no_pin` and `totp_pin` test cases. - Always return a `Result` in functions that communicate with a device. +- Combine `get_{major,minor}_firmware_version` into `get_firmware_version`. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/device.rs b/src/device.rs index 4178922..2fac4f2 100644 --- a/src/device.rs +++ b/src/device.rs @@ -379,7 +379,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { result_or_error(unsafe { nitrokey_sys::NK_get_admin_retry_count() }) } - /// Returns the major part of the firmware version (should be zero). + /// Returns the firmware version. /// /// # Example /// @@ -389,37 +389,24 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; - /// println!( - /// "Firmware version: {}.{}", - /// device.get_major_firmware_version().unwrap(), - /// device.get_minor_firmware_version().unwrap(), - /// ); + /// match device.get_firmware_version() { + /// Ok(version) => println!("Firmware version: {}", version), + /// Err(err) => println!("Could not access firmware version: {}", err), + /// }; /// # Ok(()) /// # } /// ``` - fn get_major_firmware_version(&self) -> Result { - result_or_error(unsafe { nitrokey_sys::NK_get_major_firmware_version() }) - } - - /// Returns the minor part of the firmware version (for example 8 for version 0.8). - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; - /// println!( - /// "Firmware version: {}.{}", - /// device.get_major_firmware_version().unwrap(), - /// device.get_minor_firmware_version().unwrap(), - /// ); - /// # Ok(()) - /// # } - fn get_minor_firmware_version(&self) -> Result { - result_or_error(unsafe { nitrokey_sys::NK_get_minor_firmware_version() }) + fn get_firmware_version(&self) -> Result { + 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() })?; + let max = i32::from(u8::max_value()); + if major < 0 || minor < 0 || major > max || minor > max { + return Err(Error::UnexpectedError); + } + Ok(FirmwareVersion { + major: major as u8, + minor: minor as u8, + }) } /// Returns the current configuration of the Nitrokey device. diff --git a/tests/device.rs b/tests/device.rs index 7ab4d66..d80f011 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -95,10 +95,9 @@ fn get_serial_number(device: DeviceWrapper) { } #[test_device] fn get_firmware_version(device: Pro) { - assert_ok!(0, device.get_major_firmware_version()); - let minor = device.get_minor_firmware_version(); - assert!(minor.is_ok()); - assert!(minor.unwrap() > 0); + let version = device.get_firmware_version().unwrap(); + assert_eq!(0, version.major); + assert!(version.minor > 0); } fn admin_retry(device: T, suffix: &str, count: u8) -> T { -- cgit v1.2.3 From 12b6a4d6c56cba4c2a87027d7c5f26ebe42d3315 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 28 Jan 2019 20:02:18 +0000 Subject: Prefer eprintln over println for error messages --- src/auth.rs | 6 +++--- src/device.rs | 58 +++++++++++++++++++++++++++++----------------------------- src/lib.rs | 6 +++--- src/otp.rs | 28 ++++++++++++++-------------- src/pws.rs | 6 +++--- 5 files changed, 52 insertions(+), 52 deletions(-) (limited to 'src') diff --git a/src/auth.rs b/src/auth.rs index 18b6572..8978f32 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -49,7 +49,7 @@ pub trait Authenticate { /// user.device() /// }, /// Err((device, err)) => { - /// println!("Could not authenticate as user: {}", err); + /// eprintln!("Could not authenticate as user: {}", err); /// device /// }, /// }; @@ -95,7 +95,7 @@ pub trait Authenticate { /// admin.device() /// }, /// Err((device, err)) => { - /// println!("Could not authenticate as admin: {}", err); + /// eprintln!("Could not authenticate as admin: {}", err); /// device /// }, /// }; @@ -282,7 +282,7 @@ impl Admin { /// admin.write_config(config); /// () /// }, - /// Err((_, err)) => println!("Could not authenticate as admin: {}", err), + /// Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err), /// }; /// # Ok(()) /// # } diff --git a/src/device.rs b/src/device.rs index 2fac4f2..d199d9a 100644 --- a/src/device.rs +++ b/src/device.rs @@ -78,7 +78,7 @@ impl fmt::Display for VolumeMode { /// user.device() /// }, /// Err((device, err)) => { -/// println!("Could not authenticate as user: {}", err); +/// eprintln!("Could not authenticate as user: {}", err); /// device /// }, /// }; @@ -142,7 +142,7 @@ pub enum DeviceWrapper { /// user.device() /// }, /// Err((device, err)) => { -/// println!("Could not authenticate as user: {}", err); +/// eprintln!("Could not authenticate as user: {}", err); /// device /// }, /// }; @@ -188,7 +188,7 @@ pub struct Pro { /// user.device() /// }, /// Err((device, err)) => { -/// println!("Could not authenticate as user: {}", err); +/// eprintln!("Could not authenticate as user: {}", err); /// device /// }, /// }; @@ -325,7 +325,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// let device = nitrokey::connect()?; /// match device.get_serial_number() { /// Ok(number) => println!("serial no: {}", number), - /// Err(err) => println!("Could not get serial number: {}", err), + /// Err(err) => eprintln!("Could not get serial number: {}", err), /// }; /// # Ok(()) /// # } @@ -347,7 +347,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// let device = nitrokey::connect()?; /// match device.get_user_retry_count() { /// Ok(count) => println!("{} remaining authentication attempts (user)", count), - /// Err(err) => println!("Could not get user retry count: {}", err), + /// Err(err) => eprintln!("Could not get user retry count: {}", err), /// } /// # Ok(()) /// # } @@ -370,7 +370,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// let count = device.get_admin_retry_count(); /// match device.get_admin_retry_count() { /// Ok(count) => println!("{} remaining authentication attempts (admin)", count), - /// Err(err) => println!("Could not get admin retry count: {}", err), + /// Err(err) => eprintln!("Could not get admin retry count: {}", err), /// } /// # Ok(()) /// # } @@ -391,7 +391,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// let device = nitrokey::connect()?; /// match device.get_firmware_version() { /// Ok(version) => println!("Firmware version: {}", version), - /// Err(err) => println!("Could not access firmware version: {}", err), + /// Err(err) => eprintln!("Could not access firmware version: {}", err), /// }; /// # Ok(()) /// # } @@ -455,7 +455,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// let device = nitrokey::connect()?; /// match device.change_admin_pin("12345678", "12345679") { /// Ok(()) => println!("Updated admin PIN."), - /// Err(err) => println!("Failed to update admin PIN: {}", err), + /// Err(err) => eprintln!("Failed to update admin PIN: {}", err), /// }; /// # Ok(()) /// # } @@ -488,7 +488,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// let device = nitrokey::connect()?; /// match device.change_user_pin("123456", "123457") { /// Ok(()) => println!("Updated admin PIN."), - /// Err(err) => println!("Failed to update admin PIN: {}", err), + /// Err(err) => eprintln!("Failed to update admin PIN: {}", err), /// }; /// # Ok(()) /// # } @@ -521,7 +521,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// let device = nitrokey::connect()?; /// match device.unlock_user_pin("12345678", "123456") { /// Ok(()) => println!("Unlocked user PIN."), - /// Err(err) => println!("Failed to unlock user PIN: {}", err), + /// Err(err) => eprintln!("Failed to unlock user PIN: {}", err), /// }; /// # Ok(()) /// # } @@ -555,7 +555,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// let device = nitrokey::connect()?; /// match device.lock() { /// Ok(()) => println!("Locked the Nitrokey device."), - /// Err(err) => println!("Could not lock the Nitrokey device: {}", err), + /// Err(err) => eprintln!("Could not lock the Nitrokey device: {}", err), /// }; /// # Ok(()) /// # } @@ -586,7 +586,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// let device = nitrokey::connect()?; /// match device.factory_reset("12345678") { /// Ok(()) => println!("Performed a factory reset."), - /// Err(err) => println!("Could not perform a factory reset: {}", err), + /// Err(err) => eprintln!("Could not perform a factory reset: {}", err), /// }; /// # Ok(()) /// # } @@ -620,7 +620,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// let device = nitrokey::connect()?; /// match device.build_aes_key("12345678") { /// Ok(()) => println!("New AES keys have been built."), - /// Err(err) => println!("Could not build new AES keys: {}", err), + /// Err(err) => eprintln!("Could not build new AES keys: {}", err), /// }; /// # Ok(()) /// # } @@ -649,7 +649,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// /// match nitrokey::connect() { /// Ok(device) => do_something(device), -/// Err(err) => println!("Could not connect to a Nitrokey: {}", err), +/// Err(err) => eprintln!("Could not connect to a Nitrokey: {}", err), /// } /// ``` /// @@ -681,7 +681,7 @@ pub fn connect() -> Result { /// /// match nitrokey::connect_model(Model::Pro) { /// Ok(device) => do_something(device), -/// Err(err) => println!("Could not connect to a Nitrokey Pro: {}", err), +/// Err(err) => eprintln!("Could not connect to a Nitrokey Pro: {}", err), /// } /// ``` /// @@ -785,7 +785,7 @@ impl Pro { /// /// match nitrokey::Pro::connect() { /// Ok(device) => use_pro(device), - /// Err(err) => println!("Could not connect to the Nitrokey Pro: {}", err), + /// Err(err) => eprintln!("Could not connect to the Nitrokey Pro: {}", err), /// } /// ``` /// @@ -838,7 +838,7 @@ impl Storage { /// /// match nitrokey::Storage::connect() { /// Ok(device) => use_storage(device), - /// Err(err) => println!("Could not connect to the Nitrokey Storage: {}", err), + /// Err(err) => eprintln!("Could not connect to the Nitrokey Storage: {}", err), /// } /// ``` /// @@ -878,7 +878,7 @@ impl Storage { /// let device = nitrokey::Storage::connect()?; /// match device.change_update_pin("12345678", "87654321") { /// Ok(()) => println!("Updated update PIN."), - /// Err(err) => println!("Failed to update update PIN: {}", err), + /// Err(err) => eprintln!("Failed to update update PIN: {}", err), /// }; /// # Ok(()) /// # } @@ -915,7 +915,7 @@ impl Storage { /// let device = nitrokey::Storage::connect()?; /// match device.enable_firmware_update("12345678") { /// Ok(()) => println!("Nitrokey entered update mode."), - /// Err(err) => println!("Could not enter update mode: {}", err), + /// Err(err) => eprintln!("Could not enter update mode: {}", err), /// }; /// # Ok(()) /// # } @@ -949,7 +949,7 @@ impl Storage { /// let device = nitrokey::Storage::connect()?; /// match device.enable_encrypted_volume("123456") { /// Ok(()) => println!("Enabled the encrypted volume."), - /// Err(err) => println!("Could not enable the encrypted volume: {}", err), + /// Err(err) => eprintln!("Could not enable the encrypted volume: {}", err), /// }; /// # Ok(()) /// # } @@ -983,11 +983,11 @@ impl Storage { /// match device.disable_encrypted_volume() { /// Ok(()) => println!("Disabled the encrypted volume."), /// Err(err) => { - /// println!("Could not disable the encrypted volume: {}", err); + /// eprintln!("Could not disable the encrypted volume: {}", err); /// }, /// }; /// }, - /// Err(err) => println!("Could not enable the encrypted volume: {}", err), + /// Err(err) => eprintln!("Could not enable the encrypted volume: {}", err), /// }; /// # Ok(()) /// # } @@ -1025,7 +1025,7 @@ impl Storage { /// device.enable_encrypted_volume("123445")?; /// match device.enable_hidden_volume("hidden-pw") { /// Ok(()) => println!("Enabled a hidden volume."), - /// Err(err) => println!("Could not enable the hidden volume: {}", err), + /// Err(err) => eprintln!("Could not enable the hidden volume: {}", err), /// }; /// # Ok(()) /// # } @@ -1063,11 +1063,11 @@ impl Storage { /// match device.disable_hidden_volume() { /// Ok(()) => println!("Disabled the hidden volume."), /// Err(err) => { - /// println!("Could not disable the hidden volume: {}", err); + /// eprintln!("Could not disable the hidden volume: {}", err); /// }, /// }; /// }, - /// Err(err) => println!("Could not enable the hidden volume: {}", err), + /// Err(err) => eprintln!("Could not enable the hidden volume: {}", err), /// }; /// # Ok(()) /// # } @@ -1144,7 +1144,7 @@ impl Storage { /// let device = nitrokey::Storage::connect()?; /// match device.set_unencrypted_volume_mode("123456", VolumeMode::ReadWrite) { /// Ok(()) => println!("Set the unencrypted volume to read-write mode."), - /// Err(err) => println!("Could not set the unencrypted volume to read-write mode: {}", err), + /// Err(err) => eprintln!("Could not set the unencrypted volume to read-write mode: {}", err), /// }; /// # Ok(()) /// # } @@ -1184,7 +1184,7 @@ impl Storage { /// Ok(status) => { /// println!("SD card ID: {:#x}", status.serial_number_sd_card); /// }, - /// Err(err) => println!("Could not get Storage status: {}", err), + /// Err(err) => eprintln!("Could not get Storage status: {}", err), /// }; /// # Ok(()) /// # } @@ -1228,7 +1228,7 @@ impl Storage { /// println!("SD card ID: {:#x}", data.sd_card.serial_number); /// println!("SD card size: {} GB", data.sd_card.size); /// }, - /// Err(err) => println!("Could not get Storage production info: {}", err), + /// Err(err) => eprintln!("Could not get Storage production info: {}", err), /// }; /// # Ok(()) /// # } @@ -1273,7 +1273,7 @@ impl Storage { /// let device = nitrokey::Storage::connect()?; /// match device.clear_new_sd_card_warning("12345678") { /// Ok(()) => println!("Cleared the new SD card warning."), - /// Err(err) => println!("Could not set the clear the new SD card warning: {}", err), + /// Err(err) => eprintln!("Could not set the clear the new SD card warning: {}", err), /// }; /// # Ok(()) /// # } diff --git a/src/lib.rs b/src/lib.rs index f2d524e..f7da8e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,10 +50,10 @@ //! Ok(admin) => { //! match admin.write_hotp_slot(slot_data, 0) { //! Ok(()) => println!("Successfully wrote slot."), -//! Err(err) => println!("Could not write slot: {}", err), +//! Err(err) => eprintln!("Could not write slot: {}", err), //! } //! }, -//! Err((_, err)) => println!("Could not authenticate as admin: {}", err), +//! Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err), //! } //! # Ok(()) //! # } @@ -69,7 +69,7 @@ //! let device = nitrokey::connect()?; //! match device.get_hotp_code(1) { //! Ok(code) => println!("Generated HOTP code: {}", code), -//! Err(err) => println!("Could not generate HOTP code: {}", err), +//! Err(err) => eprintln!("Could not generate HOTP code: {}", err), //! } //! # Ok(()) //! # } diff --git a/src/otp.rs b/src/otp.rs index 6e0379b..abaff66 100644 --- a/src/otp.rs +++ b/src/otp.rs @@ -41,10 +41,10 @@ pub trait ConfigureOtp { /// Ok(admin) => { /// match admin.write_hotp_slot(slot_data, 0) { /// Ok(()) => println!("Successfully wrote slot."), - /// Err(err) => println!("Could not write slot: {}", err), + /// Err(err) => eprintln!("Could not write slot: {}", err), /// } /// }, - /// Err((_, err)) => println!("Could not authenticate as admin: {}", err), + /// Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err), /// } /// # Ok(()) /// # } @@ -77,10 +77,10 @@ pub trait ConfigureOtp { /// Ok(admin) => { /// match admin.write_totp_slot(slot_data, 30) { /// Ok(()) => println!("Successfully wrote slot."), - /// Err(err) => println!("Could not write slot: {}", err), + /// Err(err) => eprintln!("Could not write slot: {}", err), /// } /// }, - /// Err((_, err)) => println!("Could not authenticate as admin: {}", err), + /// Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err), /// } /// # Ok(()) /// # } @@ -109,10 +109,10 @@ pub trait ConfigureOtp { /// Ok(admin) => { /// match admin.erase_hotp_slot(1) { /// Ok(()) => println!("Successfully erased slot."), - /// Err(err) => println!("Could not erase slot: {}", err), + /// Err(err) => eprintln!("Could not erase slot: {}", err), /// } /// }, - /// Err((_, err)) => println!("Could not authenticate as admin: {}", err), + /// Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err), /// } /// # Ok(()) /// # } @@ -139,10 +139,10 @@ pub trait ConfigureOtp { /// Ok(admin) => { /// match admin.erase_totp_slot(1) { /// Ok(()) => println!("Successfully erased slot."), - /// Err(err) => println!("Could not erase slot: {}", err), + /// Err(err) => eprintln!("Could not erase slot: {}", err), /// } /// }, - /// Err((_, err)) => println!("Could not authenticate as admin: {}", err), + /// Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err), /// } /// # Ok(()) /// # } @@ -175,7 +175,7 @@ pub trait GenerateOtp { /// let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH); /// match time { /// Ok(time) => device.set_time(time.as_secs(), false)?, - /// Err(_) => println!("The system time is before the Unix epoch!"), + /// Err(_) => eprintln!("The system time is before the Unix epoch!"), /// } /// # Ok(()) /// # } @@ -212,8 +212,8 @@ pub trait GenerateOtp { /// let device = nitrokey::connect()?; /// match device.get_hotp_slot_name(1) { /// Ok(name) => println!("HOTP slot 1: {}", name), - /// Err(Error::CommandError(CommandError::SlotNotProgrammed)) => println!("HOTP slot 1 not programmed"), - /// Err(err) => println!("Could not get slot name: {}", err), + /// Err(Error::CommandError(CommandError::SlotNotProgrammed)) => eprintln!("HOTP slot 1 not programmed"), + /// Err(err) => eprintln!("Could not get slot name: {}", err), /// }; /// # Ok(()) /// # } @@ -241,8 +241,8 @@ pub trait GenerateOtp { /// let device = nitrokey::connect()?; /// match device.get_totp_slot_name(1) { /// Ok(name) => println!("TOTP slot 1: {}", name), - /// Err(Error::CommandError(CommandError::SlotNotProgrammed)) => println!("TOTP slot 1 not programmed"), - /// Err(err) => println!("Could not get slot name: {}", err), + /// Err(Error::CommandError(CommandError::SlotNotProgrammed)) => eprintln!("TOTP slot 1 not programmed"), + /// Err(err) => eprintln!("Could not get slot name: {}", err), /// }; /// # Ok(()) /// # } @@ -313,7 +313,7 @@ pub trait GenerateOtp { /// let code = device.get_totp_code(1)?; /// println!("Generated TOTP code on slot 1: {}", code); /// }, - /// Err(_) => println!("Timestamps before 1970-01-01 are not supported!"), + /// Err(_) => eprintln!("Timestamps before 1970-01-01 are not supported!"), /// } /// # Ok(()) /// # } diff --git a/src/pws.rs b/src/pws.rs index fcf057b..1e27935 100644 --- a/src/pws.rs +++ b/src/pws.rs @@ -103,7 +103,7 @@ pub trait GetPasswordSafe { /// use_password_safe(&pws); /// device.lock()?; /// }, - /// Err(err) => println!("Could not open the password safe: {}", err), + /// Err(err) => eprintln!("Could not open the password safe: {}", err), /// }; /// # Ok(()) /// # } @@ -201,7 +201,7 @@ impl<'a> PasswordSafe<'a> { /// let password = pws.get_slot_login(0)?; /// println!("Credentials for {}: login {}, password {}", name, login, password); /// }, - /// Err(err) => println!("Could not open the password safe: {}", err), + /// Err(err) => eprintln!("Could not open the password safe: {}", err), /// }; /// # Ok(()) /// # } @@ -344,7 +344,7 @@ impl<'a> PasswordSafe<'a> { /// let pws = device.get_password_safe("123456")?; /// match pws.erase_slot(0) { /// Ok(()) => println!("Erased slot 0."), - /// Err(err) => println!("Could not erase slot 0: {}", err), + /// Err(err) => eprintln!("Could not erase slot 0: {}", err), /// }; /// # Ok(()) /// # } -- cgit v1.2.3 From ad76653b3be57c0cfd31c8056a8d68537034324e Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 31 Jan 2019 11:07:50 +0000 Subject: Add set_encrypted_volume_mode method to Storage Previously, we considered this command as unsupported as it only was available with firmware version 0.49. But as discussed in nitrocli issue 80 [0], it will probably be re-enabled in future firmware versions. Therefore this patch adds the set_encrypted_volume_mode to Storage. [0] https://github.com/d-e-s-o/nitrocli/issues/80 --- CHANGELOG.md | 1 + README.md | 3 +-- src/device.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++++- tests/device.rs | 30 ++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index c51727e..a9e3065 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ SPDX-License-Identifier: MIT - Fix timing issues with the `totp_no_pin` and `totp_pin` test cases. - Always return a `Result` in functions that communicate with a device. - Combine `get_{major,minor}_firmware_version` into `get_firmware_version`. +- Add `set_encrypted_volume_mode` to `Storage`. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/README.md b/README.md index 069fed1..8c596eb 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,7 @@ supported by `nitrokey-rs`: - `NK_is_AES_supported`. This method is no longer needed for Nitrokey devices with a recent firmware version. - `NK_set_unencrypted_volume_rorw_pin_type_user`, - `NK_set_unencrypted_read_only`, `NK_set_unencrypted_read_write`, - `NK_set_encrypted_read_only` and `NK_set_encrypted_read_write`. These + `NK_set_unencrypted_read_only`, `NK_set_unencrypted_read_write`. These methods are only relevant for older firmware versions (pre-v0.51). As the Nitrokey Storage firmware can be updated easily, we do not support these outdated versions. diff --git a/src/device.rs b/src/device.rs index d199d9a..c985802 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1142,7 +1142,7 @@ impl Storage { /// /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::Storage::connect()?; - /// match device.set_unencrypted_volume_mode("123456", VolumeMode::ReadWrite) { + /// match device.set_unencrypted_volume_mode("12345678", VolumeMode::ReadWrite) { /// Ok(()) => println!("Set the unencrypted volume to read-write mode."), /// Err(err) => eprintln!("Could not set the unencrypted volume to read-write mode: {}", err), /// }; @@ -1169,6 +1169,51 @@ impl Storage { get_command_result(result) } + /// Sets the access mode of the encrypted volume. + /// + /// This command will reconnect the encrypted volume so buffers should be flushed before + /// calling it. It is only available in firmware version 0.49. + /// + /// # Errors + /// + /// - [`InvalidString`][] if the provided password contains a null byte + /// - [`WrongPassword`][] if the provided admin password is wrong + /// + /// # Example + /// + /// ```no_run + /// # use nitrokey::Error; + /// use nitrokey::VolumeMode; + /// + /// # fn try_main() -> Result<(), Error> { + /// let device = nitrokey::Storage::connect()?; + /// match device.set_encrypted_volume_mode("12345678", VolumeMode::ReadWrite) { + /// Ok(()) => println!("Set the encrypted volume to read-write mode."), + /// Err(err) => eprintln!("Could not set the encrypted volume to read-write mode: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString + /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword + pub fn set_encrypted_volume_mode( + &self, + 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) + } + /// Returns the status of the connected storage device. /// /// # Example diff --git a/tests/device.rs b/tests/device.rs index 306b33f..969a7df 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -401,6 +401,36 @@ fn lock(device: Storage) { assert_eq!(1, count_nitrokey_block_devices()); } +#[test_device] +fn set_encrypted_volume_mode(device: Storage) { + // This test case does not check the device status as the command only works with firmware + // version 0.49. For later versions, it does not do anything and always returns Ok(()). + + assert_ok!( + (), + device.set_encrypted_volume_mode(ADMIN_PASSWORD, VolumeMode::ReadOnly) + ); + + // TODO: re-enable once the password is checked in the firmware + // assert_cmd_err!( + // CommandError::WrongPassword, + // device.set_encrypted_volume_mode(USER_PASSWORD, VolumeMode::ReadOnly) + // ); + + assert_ok!( + (), + device.set_encrypted_volume_mode(ADMIN_PASSWORD, VolumeMode::ReadOnly) + ); + assert_ok!( + (), + device.set_encrypted_volume_mode(ADMIN_PASSWORD, VolumeMode::ReadWrite) + ); + assert_ok!( + (), + device.set_encrypted_volume_mode(ADMIN_PASSWORD, VolumeMode::ReadOnly) + ); +} + #[test_device] fn set_unencrypted_volume_mode(device: Storage) { fn assert_mode(device: &Storage, mode: VolumeMode) { -- cgit v1.2.3 From e97ccf213eec4e2d056c2f72079e4eeb7ac66f3f Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 28 Jan 2019 12:05:42 +0000 Subject: Implement DerefMut for User and Admin As we want to change some methods to take a mutable reference to a Device, we implement DerefMut for User and Admin so that users can obtain a mutable reference to the wrapped device. --- CHANGELOG.md | 2 ++ src/auth.rs | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index a9e3065..fcba0f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ SPDX-License-Identifier: MIT - Always return a `Result` in functions that communicate with a device. - Combine `get_{major,minor}_firmware_version` into `get_firmware_version`. - Add `set_encrypted_volume_mode` to `Storage`. +- Use mutability to represent changes to the device status: + - Implement `DerefMut` for `User` and `Admin`. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/auth.rs b/src/auth.rs index 8978f32..8cec49c 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,7 +1,7 @@ // Copyright (C) 2018-2019 Robin Krahl // SPDX-License-Identifier: MIT -use std::ops::Deref; +use std::ops; use std::os::raw::c_char; use std::os::raw::c_int; @@ -211,7 +211,7 @@ impl User { } } -impl Deref for User { +impl ops::Deref for User { type Target = T; fn deref(&self) -> &Self::Target { @@ -219,6 +219,12 @@ impl Deref for User { } } +impl ops::DerefMut for User { + fn deref_mut(&mut self) -> &mut T { + &mut self.device + } +} + impl GenerateOtp for User { fn get_hotp_code(&self, slot: u8) -> Result { result_from_string(unsafe { @@ -246,7 +252,7 @@ impl AuthenticatedDevice for User { } } -impl Deref for Admin { +impl ops::Deref for Admin { type Target = T; fn deref(&self) -> &Self::Target { @@ -254,6 +260,12 @@ impl Deref for Admin { } } +impl ops::DerefMut for Admin { + fn deref_mut(&mut self) -> &mut T { + &mut self.device + } +} + impl Admin { /// Forgets the user authentication and returns an unauthenticated device. This method /// consumes the authenticated device. It does not perform any actual commands on the -- cgit v1.2.3 From eef2118717878f3543248ebf2d099aebbedceacf Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 30 Jan 2019 16:02:49 +0000 Subject: Add device_mut method to DeviceWrapper To prepare the mutability refactoring, we add a device_mut method to DeviceWrapper that can be used to obtain a mutable reference to the wrapped device. --- CHANGELOG.md | 1 + src/device.rs | 7 +++++++ 2 files changed, 8 insertions(+) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index fcba0f3..718b796 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ SPDX-License-Identifier: MIT - Add `set_encrypted_volume_mode` to `Storage`. - Use mutability to represent changes to the device status: - Implement `DerefMut` for `User` and `Admin`. + - Add `device_mut` method to `DeviceWrapper`. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/device.rs b/src/device.rs index c985802..462e9dc 100644 --- a/src/device.rs +++ b/src/device.rs @@ -728,6 +728,13 @@ impl DeviceWrapper { DeviceWrapper::Pro(ref pro) => pro, } } + + fn device_mut(&mut self) -> &mut dyn Device { + match *self { + DeviceWrapper::Storage(ref mut storage) => storage, + DeviceWrapper::Pro(ref mut pro) => pro, + } + } } impl From for DeviceWrapper { -- cgit v1.2.3 From f49e61589e32217f97c94aa86d826f6b65170fba Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 28 Jan 2019 12:27:15 +0000 Subject: Require mutable reference if method changes device state Previously, all methods that access a Nitrokey device took a reference to the device as input. This method changes methods that change the device state to require a mutable reference instead. In most case, this is straightforward as the method writes data to the device (for example write_config or change_user_pin). But there are two edge cases: - Authenticating with a PIN changes the device state as it may decrease the PIN retry counter if the authentication fails. - Generating an HOTP code changes the device state as it increases the HOTP counter. --- CHANGELOG.md | 1 + src/auth.rs | 14 ++++----- src/device.rs | 72 ++++++++++++++++++++++---------------------- src/lib.rs | 4 +-- src/otp.rs | 26 ++++++++-------- src/pws.rs | 33 ++++++++++---------- tests/device.rs | 46 +++++++++++++++++----------- tests/otp.rs | 93 ++++++++++++++++++++++++++++----------------------------- tests/pws.rs | 18 +++++++---- 9 files changed, 163 insertions(+), 144 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 718b796..e98e857 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ SPDX-License-Identifier: MIT - Use mutability to represent changes to the device status: - Implement `DerefMut` for `User` and `Admin`. - Add `device_mut` method to `DeviceWrapper`. + - Require a mutable `Device` reference if a method changes the device state. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/auth.rs b/src/auth.rs index 8cec49c..f9f50fa 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -226,7 +226,7 @@ impl ops::DerefMut for User { } impl GenerateOtp for User { - fn get_hotp_code(&self, slot: u8) -> Result { + fn get_hotp_code(&mut self, slot: u8) -> Result { result_from_string(unsafe { nitrokey_sys::NK_get_hotp_code_PIN(slot, self.temp_password_ptr()) }) @@ -290,7 +290,7 @@ impl Admin { /// let device = nitrokey::connect()?; /// let config = Config::new(None, None, None, false); /// match device.authenticate_admin("12345678") { - /// Ok(admin) => { + /// Ok(mut admin) => { /// admin.write_config(config); /// () /// }, @@ -301,7 +301,7 @@ impl Admin { /// ``` /// /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot - pub fn write_config(&self, config: Config) -> Result<(), Error> { + pub fn write_config(&mut self, config: Config) -> Result<(), Error> { let raw_config = RawConfig::try_from(config)?; get_command_result(unsafe { nitrokey_sys::NK_write_config( @@ -317,7 +317,7 @@ impl Admin { } impl ConfigureOtp for Admin { - fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> Result<(), Error> { + 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( @@ -334,7 +334,7 @@ impl ConfigureOtp for Admin { }) } - fn write_totp_slot(&self, data: OtpSlotData, time_window: u16) -> Result<(), Error> { + 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( @@ -351,13 +351,13 @@ impl ConfigureOtp for Admin { }) } - fn erase_hotp_slot(&self, slot: u8) -> Result<(), Error> { + fn erase_hotp_slot(&mut self, slot: u8) -> Result<(), Error> { get_command_result(unsafe { nitrokey_sys::NK_erase_hotp_slot(slot, self.temp_password_ptr()) }) } - fn erase_totp_slot(&self, slot: u8) -> Result<(), Error> { + fn erase_totp_slot(&mut self, slot: u8) -> Result<(), Error> { get_command_result(unsafe { nitrokey_sys::NK_erase_totp_slot(slot, self.temp_password_ptr()) }) diff --git a/src/device.rs b/src/device.rs index 462e9dc..f6492cd 100644 --- a/src/device.rs +++ b/src/device.rs @@ -452,7 +452,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// match device.change_admin_pin("12345678", "12345679") { /// Ok(()) => println!("Updated admin PIN."), /// Err(err) => eprintln!("Failed to update admin PIN: {}", err), @@ -463,7 +463,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn change_admin_pin(&self, current: &str, new: &str) -> Result<(), Error> { + 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 { @@ -485,7 +485,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// match device.change_user_pin("123456", "123457") { /// Ok(()) => println!("Updated admin PIN."), /// Err(err) => eprintln!("Failed to update admin PIN: {}", err), @@ -496,7 +496,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn change_user_pin(&self, current: &str, new: &str) -> Result<(), Error> { + 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 { @@ -518,7 +518,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// match device.unlock_user_pin("12345678", "123456") { /// Ok(()) => println!("Unlocked user PIN."), /// Err(err) => eprintln!("Failed to unlock user PIN: {}", err), @@ -529,7 +529,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn unlock_user_pin(&self, admin_pin: &str, user_pin: &str) -> Result<(), Error> { + 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 { @@ -552,7 +552,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// match device.lock() { /// Ok(()) => println!("Locked the Nitrokey device."), /// Err(err) => eprintln!("Could not lock the Nitrokey device: {}", err), @@ -560,7 +560,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # Ok(()) /// # } /// ``` - fn lock(&self) -> Result<(), Error> { + fn lock(&mut self) -> Result<(), Error> { get_command_result(unsafe { nitrokey_sys::NK_lock_device() }) } @@ -583,7 +583,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// match device.factory_reset("12345678") { /// Ok(()) => println!("Performed a factory reset."), /// Err(err) => eprintln!("Could not perform a factory reset: {}", err), @@ -593,7 +593,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// ``` /// /// [`build_aes_key`]: #method.build_aes_key - fn factory_reset(&self, admin_pin: &str) -> Result<(), Error> { + 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()) }) } @@ -617,7 +617,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// match device.build_aes_key("12345678") { /// Ok(()) => println!("New AES keys have been built."), /// Err(err) => eprintln!("Could not build new AES keys: {}", err), @@ -627,7 +627,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// ``` /// /// [`factory_reset`]: #method.factory_reset - fn build_aes_key(&self, admin_pin: &str) -> Result<(), Error> { + 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()) }) } @@ -758,8 +758,8 @@ impl GenerateOtp for DeviceWrapper { self.device().get_totp_slot_name(slot) } - fn get_hotp_code(&self, slot: u8) -> Result { - self.device().get_hotp_code(slot) + fn get_hotp_code(&mut self, slot: u8) -> Result { + self.device_mut().get_hotp_code(slot) } fn get_totp_code(&self, slot: u8) -> Result { @@ -882,7 +882,7 @@ impl Storage { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::Storage::connect()?; + /// let mut device = nitrokey::Storage::connect()?; /// match device.change_update_pin("12345678", "87654321") { /// Ok(()) => println!("Updated update PIN."), /// Err(err) => eprintln!("Failed to update update PIN: {}", err), @@ -893,7 +893,7 @@ impl Storage { /// /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - pub fn change_update_pin(&self, current: &str, new: &str) -> Result<(), Error> { + 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 { @@ -919,7 +919,7 @@ impl Storage { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::Storage::connect()?; + /// let mut device = nitrokey::Storage::connect()?; /// match device.enable_firmware_update("12345678") { /// Ok(()) => println!("Nitrokey entered update mode."), /// Err(err) => eprintln!("Could not enter update mode: {}", err), @@ -930,7 +930,7 @@ impl Storage { /// /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - pub fn enable_firmware_update(&self, update_pin: &str) -> Result<(), Error> { + 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()) @@ -953,7 +953,7 @@ impl Storage { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::Storage::connect()?; + /// let mut device = nitrokey::Storage::connect()?; /// match device.enable_encrypted_volume("123456") { /// Ok(()) => println!("Enabled the encrypted volume."), /// Err(err) => eprintln!("Could not enable the encrypted volume: {}", err), @@ -964,7 +964,7 @@ impl Storage { /// /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - pub fn enable_encrypted_volume(&self, user_pin: &str) -> Result<(), Error> { + 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()) }) } @@ -982,7 +982,7 @@ impl Storage { /// fn use_volume() {} /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::Storage::connect()?; + /// let mut device = nitrokey::Storage::connect()?; /// match device.enable_encrypted_volume("123456") { /// Ok(()) => { /// println!("Enabled the encrypted volume."); @@ -999,7 +999,7 @@ impl Storage { /// # Ok(()) /// # } /// ``` - pub fn disable_encrypted_volume(&self) -> Result<(), Error> { + pub fn disable_encrypted_volume(&mut self) -> Result<(), Error> { get_command_result(unsafe { nitrokey_sys::NK_lock_encrypted_volume() }) } @@ -1028,7 +1028,7 @@ impl Storage { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::Storage::connect()?; + /// let mut device = nitrokey::Storage::connect()?; /// device.enable_encrypted_volume("123445")?; /// match device.enable_hidden_volume("hidden-pw") { /// Ok(()) => println!("Enabled a hidden volume."), @@ -1041,7 +1041,7 @@ impl Storage { /// [`enable_encrypted_volume`]: #method.enable_encrypted_volume /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString - pub fn enable_hidden_volume(&self, volume_password: &str) -> Result<(), Error> { + 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()) @@ -1061,7 +1061,7 @@ impl Storage { /// fn use_volume() {} /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::Storage::connect()?; + /// let mut device = nitrokey::Storage::connect()?; /// device.enable_encrypted_volume("123445")?; /// match device.enable_hidden_volume("hidden-pw") { /// Ok(()) => { @@ -1079,7 +1079,7 @@ impl Storage { /// # Ok(()) /// # } /// ``` - pub fn disable_hidden_volume(&self) -> Result<(), Error> { + pub fn disable_hidden_volume(&mut self) -> Result<(), Error> { get_command_result(unsafe { nitrokey_sys::NK_lock_hidden_volume() }) } @@ -1108,7 +1108,7 @@ impl Storage { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::Storage::connect()?; + /// let mut device = nitrokey::Storage::connect()?; /// device.enable_encrypted_volume("123445")?; /// device.create_hidden_volume(0, 0, 100, "hidden-pw")?; /// # Ok(()) @@ -1118,7 +1118,7 @@ impl Storage { /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString pub fn create_hidden_volume( - &self, + &mut self, slot: u8, start: u8, end: u8, @@ -1148,7 +1148,7 @@ impl Storage { /// use nitrokey::VolumeMode; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::Storage::connect()?; + /// let mut device = nitrokey::Storage::connect()?; /// match device.set_unencrypted_volume_mode("12345678", VolumeMode::ReadWrite) { /// Ok(()) => println!("Set the unencrypted volume to read-write mode."), /// Err(err) => eprintln!("Could not set the unencrypted volume to read-write mode: {}", err), @@ -1160,7 +1160,7 @@ impl Storage { /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword pub fn set_unencrypted_volume_mode( - &self, + &mut self, admin_pin: &str, mode: VolumeMode, ) -> Result<(), Error> { @@ -1193,7 +1193,7 @@ impl Storage { /// use nitrokey::VolumeMode; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::Storage::connect()?; + /// let mut device = nitrokey::Storage::connect()?; /// match device.set_encrypted_volume_mode("12345678", VolumeMode::ReadWrite) { /// Ok(()) => println!("Set the encrypted volume to read-write mode."), /// Err(err) => eprintln!("Could not set the encrypted volume to read-write mode: {}", err), @@ -1205,7 +1205,7 @@ impl Storage { /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword pub fn set_encrypted_volume_mode( - &self, + &mut self, admin_pin: &str, mode: VolumeMode, ) -> Result<(), Error> { @@ -1322,7 +1322,7 @@ impl Storage { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::Storage::connect()?; + /// let mut device = nitrokey::Storage::connect()?; /// match device.clear_new_sd_card_warning("12345678") { /// Ok(()) => println!("Cleared the new SD card warning."), /// Err(err) => eprintln!("Could not set the clear the new SD card warning: {}", err), @@ -1333,7 +1333,7 @@ impl Storage { /// /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - pub fn clear_new_sd_card_warning(&self, admin_pin: &str) -> Result<(), Error> { + 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()) @@ -1341,7 +1341,7 @@ impl Storage { } /// Blinks the red and green LED alternatively and infinitely until the device is reconnected. - pub fn wink(&self) -> Result<(), Error> { + pub fn wink(&mut self) -> Result<(), Error> { get_command_result(unsafe { nitrokey_sys::NK_wink() }) } @@ -1361,7 +1361,7 @@ impl Storage { /// /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - pub fn export_firmware(&self, admin_pin: &str) -> Result<(), Error> { + 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()) }) } diff --git a/src/lib.rs b/src/lib.rs index f7da8e1..c35829c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,7 +47,7 @@ //! let device = nitrokey::connect()?; //! let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits); //! match device.authenticate_admin("12345678") { -//! Ok(admin) => { +//! Ok(mut admin) => { //! match admin.write_hotp_slot(slot_data, 0) { //! Ok(()) => println!("Successfully wrote slot."), //! Err(err) => eprintln!("Could not write slot: {}", err), @@ -66,7 +66,7 @@ //! # use nitrokey::Error; //! //! # fn try_main() -> Result<(), Error> { -//! let device = nitrokey::connect()?; +//! let mut device = nitrokey::connect()?; //! match device.get_hotp_code(1) { //! Ok(code) => println!("Generated HOTP code: {}", code), //! Err(err) => eprintln!("Could not generate HOTP code: {}", err), diff --git a/src/otp.rs b/src/otp.rs index abaff66..ee142c7 100644 --- a/src/otp.rs +++ b/src/otp.rs @@ -38,7 +38,7 @@ pub trait ConfigureOtp { /// let device = nitrokey::connect()?; /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits); /// match device.authenticate_admin("12345678") { - /// Ok(admin) => { + /// Ok(mut admin) => { /// match admin.write_hotp_slot(slot_data, 0) { /// Ok(()) => println!("Successfully wrote slot."), /// Err(err) => eprintln!("Could not write slot: {}", err), @@ -53,7 +53,7 @@ pub trait ConfigureOtp { /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`NoName`]: enum.CommandError.html#variant.NoName - fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> Result<(), Error>; + fn write_hotp_slot(&mut self, data: OtpSlotData, counter: u64) -> Result<(), Error>; /// Configure a TOTP slot with the given data and set the TOTP time window to the given value /// (default 30). @@ -74,7 +74,7 @@ pub trait ConfigureOtp { /// let device = nitrokey::connect()?; /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::EightDigits); /// match device.authenticate_admin("12345678") { - /// Ok(admin) => { + /// Ok(mut admin) => { /// match admin.write_totp_slot(slot_data, 30) { /// Ok(()) => println!("Successfully wrote slot."), /// Err(err) => eprintln!("Could not write slot: {}", err), @@ -89,7 +89,7 @@ pub trait ConfigureOtp { /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`NoName`]: enum.CommandError.html#variant.NoName - fn write_totp_slot(&self, data: OtpSlotData, time_window: u16) -> Result<(), Error>; + fn write_totp_slot(&mut self, data: OtpSlotData, time_window: u16) -> Result<(), Error>; /// Erases an HOTP slot. /// @@ -106,7 +106,7 @@ pub trait ConfigureOtp { /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// match device.authenticate_admin("12345678") { - /// Ok(admin) => { + /// Ok(mut admin) => { /// match admin.erase_hotp_slot(1) { /// Ok(()) => println!("Successfully erased slot."), /// Err(err) => eprintln!("Could not erase slot: {}", err), @@ -119,7 +119,7 @@ pub trait ConfigureOtp { /// ``` /// /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot - fn erase_hotp_slot(&self, slot: u8) -> Result<(), Error>; + fn erase_hotp_slot(&mut self, slot: u8) -> Result<(), Error>; /// Erases a TOTP slot. /// @@ -136,7 +136,7 @@ pub trait ConfigureOtp { /// # fn try_main() -> Result<(), Error> { /// let device = nitrokey::connect()?; /// match device.authenticate_admin("12345678") { - /// Ok(admin) => { + /// Ok(mut admin) => { /// match admin.erase_totp_slot(1) { /// Ok(()) => println!("Successfully erased slot."), /// Err(err) => eprintln!("Could not erase slot: {}", err), @@ -149,7 +149,7 @@ pub trait ConfigureOtp { /// ``` /// /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot - fn erase_totp_slot(&self, slot: u8) -> Result<(), Error>; + fn erase_totp_slot(&mut self, slot: u8) -> Result<(), Error>; } /// Provides methods to generate OTP codes and to query OTP slots on a Nitrokey @@ -171,7 +171,7 @@ pub trait GenerateOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH); /// match time { /// Ok(time) => device.set_time(time.as_secs(), false)?, @@ -187,7 +187,7 @@ pub trait GenerateOtp { /// /// [`get_totp_code`]: #method.get_totp_code /// [`Timestamp`]: enum.CommandError.html#variant.Timestamp - fn set_time(&self, time: u64, force: bool) -> Result<(), Error> { + fn set_time(&mut self, time: u64, force: bool) -> Result<(), Error> { let result = if force { unsafe { nitrokey_sys::NK_totp_set_time(time) } } else { @@ -270,7 +270,7 @@ pub trait GenerateOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// let code = device.get_hotp_code(1)?; /// println!("Generated HOTP code on slot 1: {}", code); /// # Ok(()) @@ -281,7 +281,7 @@ pub trait GenerateOtp { /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot /// [`NotAuthorized`]: enum.CommandError.html#variant.NotAuthorized /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed - fn get_hotp_code(&self, slot: u8) -> Result { + fn get_hotp_code(&mut self, slot: u8) -> Result { result_from_string(unsafe { nitrokey_sys::NK_get_hotp_code(slot) }) } @@ -305,7 +305,7 @@ pub trait GenerateOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH); /// match time { /// Ok(time) => { diff --git a/src/pws.rs b/src/pws.rs index 1e27935..371de6e 100644 --- a/src/pws.rs +++ b/src/pws.rs @@ -43,9 +43,10 @@ pub const SLOT_COUNT: u8 = 16; /// } /// /// # fn try_main() -> Result<(), Error> { -/// let device = nitrokey::connect()?; +/// let mut device = nitrokey::connect()?; /// let pws = device.get_password_safe("123456")?; /// use_password_safe(&pws); +/// drop(pws); /// device.lock()?; /// # Ok(()) /// # } @@ -97,14 +98,14 @@ pub trait GetPasswordSafe { /// fn use_password_safe(pws: &PasswordSafe) {} /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// match device.get_password_safe("123456") { /// Ok(pws) => { /// use_password_safe(&pws); - /// device.lock()?; /// }, /// Err(err) => eprintln!("Could not open the password safe: {}", err), /// }; + /// device.lock()?; /// # Ok(()) /// # } /// ``` @@ -116,7 +117,7 @@ pub trait GetPasswordSafe { /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`Unknown`]: enum.CommandError.html#variant.Unknown /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn get_password_safe(&self, user_pin: &str) -> Result, Error>; + fn get_password_safe(&mut self, user_pin: &str) -> Result, Error>; } fn get_password_safe<'a>( @@ -148,7 +149,7 @@ impl<'a> PasswordSafe<'a> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// let pws = device.get_password_safe("123456")?; /// pws.get_slot_status()?.iter().enumerate().for_each(|(slot, programmed)| { /// let status = match *programmed { @@ -193,7 +194,7 @@ impl<'a> PasswordSafe<'a> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// match device.get_password_safe("123456") { /// Ok(pws) => { /// let name = pws.get_slot_name(0)?; @@ -230,7 +231,7 @@ impl<'a> PasswordSafe<'a> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// let pws = device.get_password_safe("123456")?; /// let name = pws.get_slot_name(0)?; /// let login = pws.get_slot_login(0)?; @@ -263,7 +264,7 @@ impl<'a> PasswordSafe<'a> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// let pws = device.get_password_safe("123456")?; /// let name = pws.get_slot_name(0)?; /// let login = pws.get_slot_login(0)?; @@ -294,7 +295,7 @@ impl<'a> PasswordSafe<'a> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// let pws = device.get_password_safe("123456")?; /// let name = pws.get_slot_name(0)?; /// let login = pws.get_slot_login(0)?; @@ -307,7 +308,7 @@ impl<'a> PasswordSafe<'a> { /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString pub fn write_slot( - &self, + &mut self, slot: u8, name: &str, login: &str, @@ -340,8 +341,8 @@ impl<'a> PasswordSafe<'a> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; - /// let pws = device.get_password_safe("123456")?; + /// let mut device = nitrokey::connect()?; + /// let mut pws = device.get_password_safe("123456")?; /// match pws.erase_slot(0) { /// Ok(()) => println!("Erased slot 0."), /// Err(err) => eprintln!("Could not erase slot 0: {}", err), @@ -351,7 +352,7 @@ impl<'a> PasswordSafe<'a> { /// ``` /// /// [`InvalidSlot`]: enum.LibraryError.html#variant.InvalidSlot - pub fn erase_slot(&self, slot: u8) -> Result<(), Error> { + pub fn erase_slot(&mut self, slot: u8) -> Result<(), Error> { get_command_result(unsafe { nitrokey_sys::NK_erase_password_safe_slot(slot) }) } } @@ -364,19 +365,19 @@ impl<'a> Drop for PasswordSafe<'a> { } impl GetPasswordSafe for Pro { - fn get_password_safe(&self, user_pin: &str) -> Result, Error> { + fn get_password_safe(&mut self, user_pin: &str) -> Result, Error> { get_password_safe(self, user_pin) } } impl GetPasswordSafe for Storage { - fn get_password_safe(&self, user_pin: &str) -> Result, Error> { + fn get_password_safe(&mut self, user_pin: &str) -> Result, Error> { get_password_safe(self, user_pin) } } impl GetPasswordSafe for DeviceWrapper { - fn get_password_safe(&self, user_pin: &str) -> Result, Error> { + fn get_password_safe(&mut self, user_pin: &str) -> Result, Error> { get_password_safe(self, user_pin) } } diff --git a/tests/device.rs b/tests/device.rs index 969a7df..7a69214 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -133,7 +133,7 @@ fn get_retry_count(device: DeviceWrapper) { #[test_device] fn config(device: DeviceWrapper) { - let admin = unwrap_ok!(device.authenticate_admin(ADMIN_PASSWORD)); + let mut admin = unwrap_ok!(device.authenticate_admin(ADMIN_PASSWORD)); let config = Config::new(None, None, None, true); assert_ok!((), admin.write_config(config)); @@ -156,6 +156,7 @@ fn change_user_pin(device: DeviceWrapper) { let device = device.authenticate_user(USER_PASSWORD).unwrap().device(); let device = device.authenticate_user(USER_NEW_PASSWORD).unwrap_err().0; + let mut device = device; assert_ok!((), device.change_user_pin(USER_PASSWORD, USER_NEW_PASSWORD)); let device = device.authenticate_user(USER_PASSWORD).unwrap_err().0; @@ -164,6 +165,7 @@ fn change_user_pin(device: DeviceWrapper) { .unwrap() .device(); + let mut device = device; let result = device.change_user_pin(USER_PASSWORD, USER_PASSWORD); assert_cmd_err!(CommandError::WrongPassword, result); @@ -176,7 +178,7 @@ fn change_user_pin(device: DeviceWrapper) { #[test_device] fn change_admin_pin(device: DeviceWrapper) { let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device(); - let device = device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err().0; + let mut device = device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err().0; assert_ok!( (), @@ -184,7 +186,7 @@ fn change_admin_pin(device: DeviceWrapper) { ); let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap_err().0; - let device = device + let mut device = device .authenticate_admin(ADMIN_NEW_PASSWORD) .unwrap() .device(); @@ -220,7 +222,7 @@ where #[test_device] fn unlock_user_pin(device: DeviceWrapper) { - let device = device.authenticate_user(USER_PASSWORD).unwrap().device(); + let mut device = device.authenticate_user(USER_PASSWORD).unwrap().device(); assert_ok!((), device.unlock_user_pin(ADMIN_PASSWORD, USER_PASSWORD)); assert_cmd_err!( CommandError::WrongPassword, @@ -232,7 +234,7 @@ fn unlock_user_pin(device: DeviceWrapper) { let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); - let device = require_failed_user_login(device, USER_PASSWORD, CommandError::WrongPassword); + let mut device = require_failed_user_login(device, USER_PASSWORD, CommandError::WrongPassword); // unblock with current PIN assert_cmd_err!( @@ -246,7 +248,7 @@ fn unlock_user_pin(device: DeviceWrapper) { let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); - let device = require_failed_user_login(device, USER_PASSWORD, CommandError::WrongPassword); + let mut device = require_failed_user_login(device, USER_PASSWORD, CommandError::WrongPassword); // unblock with new PIN assert_cmd_err!( @@ -272,12 +274,12 @@ fn assert_utf8_err_or_ne(left: &str, right: Result) { #[test_device] fn factory_reset(device: DeviceWrapper) { - let admin = unwrap_ok!(device.authenticate_admin(ADMIN_PASSWORD)); + let mut admin = unwrap_ok!(device.authenticate_admin(ADMIN_PASSWORD)); let otp_data = OtpSlotData::new(1, "test", "0123468790", OtpMode::SixDigits); assert_ok!((), admin.write_totp_slot(otp_data, 30)); - let device = admin.device(); - let pws = unwrap_ok!(device.get_password_safe(USER_PASSWORD)); + let mut device = admin.device(); + let mut pws = unwrap_ok!(device.get_password_safe(USER_PASSWORD)); assert_ok!((), pws.write_slot(0, "test", "testlogin", "testpw")); drop(pws); @@ -302,18 +304,20 @@ fn factory_reset(device: DeviceWrapper) { let user = unwrap_ok!(device.authenticate_user(USER_PASSWORD)); assert_cmd_err!(CommandError::SlotNotProgrammed, user.get_totp_slot_name(1)); - let device = user.device(); + let mut device = user.device(); let pws = unwrap_ok!(device.get_password_safe(USER_PASSWORD)); assert_utf8_err_or_ne("test", pws.get_slot_name(0)); assert_utf8_err_or_ne("testlogin", pws.get_slot_login(0)); assert_utf8_err_or_ne("testpw", pws.get_slot_password(0)); + drop(pws); assert_ok!((), device.build_aes_key(ADMIN_PASSWORD)); } #[test_device] fn build_aes_key(device: DeviceWrapper) { - let pws = unwrap_ok!(device.get_password_safe(USER_PASSWORD)); + let mut device = device; + let mut pws = unwrap_ok!(device.get_password_safe(USER_PASSWORD)); assert_ok!((), pws.write_slot(0, "test", "testlogin", "testpw")); drop(pws); @@ -323,7 +327,7 @@ fn build_aes_key(device: DeviceWrapper) { ); assert_ok!((), device.build_aes_key(ADMIN_PASSWORD)); - let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device(); + let mut device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device(); let pws = unwrap_ok!(device.get_password_safe(USER_PASSWORD)); assert_utf8_err_or_ne("test", pws.get_slot_name(0)); @@ -333,6 +337,7 @@ fn build_aes_key(device: DeviceWrapper) { #[test_device] fn change_update_pin(device: Storage) { + let mut device = device; assert_cmd_err!( CommandError::WrongPassword, device.change_update_pin(UPDATE_NEW_PIN, UPDATE_PIN) @@ -343,6 +348,7 @@ fn change_update_pin(device: Storage) { #[test_device] fn encrypted_volume(device: Storage) { + let mut device = device; assert_ok!((), device.lock()); assert_eq!(1, count_nitrokey_block_devices()); @@ -361,6 +367,7 @@ fn encrypted_volume(device: Storage) { #[test_device] fn hidden_volume(device: Storage) { + let mut device = device; assert_ok!((), device.lock()); assert_eq!(1, count_nitrokey_block_devices()); @@ -396,6 +403,7 @@ fn hidden_volume(device: Storage) { #[test_device] fn lock(device: Storage) { + let mut device = device; assert_ok!((), device.enable_encrypted_volume(USER_PASSWORD)); assert_ok!((), device.lock()); assert_eq!(1, count_nitrokey_block_devices()); @@ -405,6 +413,7 @@ fn lock(device: Storage) { fn set_encrypted_volume_mode(device: Storage) { // This test case does not check the device status as the command only works with firmware // version 0.49. For later versions, it does not do anything and always returns Ok(()). + let mut device = device; assert_ok!( (), @@ -441,12 +450,13 @@ fn set_unencrypted_volume_mode(device: Storage) { ); } - fn assert_success(device: &Storage, mode: VolumeMode) { + fn assert_success(device: &mut Storage, mode: VolumeMode) { assert_ok!((), device.set_unencrypted_volume_mode(ADMIN_PASSWORD, mode)); assert_mode(&device, mode); } - assert_success(&device, VolumeMode::ReadOnly); + let mut device = device; + assert_success(&mut device, VolumeMode::ReadOnly); assert_cmd_err!( CommandError::WrongPassword, @@ -454,9 +464,9 @@ fn set_unencrypted_volume_mode(device: Storage) { ); assert_mode(&device, VolumeMode::ReadOnly); - assert_success(&device, VolumeMode::ReadWrite); - assert_success(&device, VolumeMode::ReadWrite); - assert_success(&device, VolumeMode::ReadOnly); + assert_success(&mut device, VolumeMode::ReadWrite); + assert_success(&mut device, VolumeMode::ReadWrite); + assert_success(&mut device, VolumeMode::ReadOnly); } #[test_device] @@ -488,6 +498,7 @@ fn get_production_info(device: Storage) { #[test_device] fn clear_new_sd_card_warning(device: Storage) { + let mut device = device; assert_ok!((), device.factory_reset(ADMIN_PASSWORD)); thread::sleep(time::Duration::from_secs(3)); assert_ok!((), device.build_aes_key(ADMIN_PASSWORD)); @@ -506,6 +517,7 @@ fn clear_new_sd_card_warning(device: Storage) { #[test_device] fn export_firmware(device: Storage) { + let mut device = device; assert_cmd_err!( CommandError::WrongPassword, device.export_firmware("someadminpn") diff --git a/tests/otp.rs b/tests/otp.rs index fc0e79e..28a8d7c 100644 --- a/tests/otp.rs +++ b/tests/otp.rs @@ -4,7 +4,7 @@ mod util; use std::fmt::Debug; -use std::ops::Deref; +use std::ops::DerefMut; use nitrokey::{ Admin, Authenticate, CommandError, Config, ConfigureOtp, Device, GenerateOtp, LibraryError, @@ -48,12 +48,12 @@ where .expect("Could not login as admin.") } -fn configure_hotp(admin: &ConfigureOtp, counter: u8) { +fn configure_hotp(admin: &mut ConfigureOtp, counter: u8) { let slot_data = OtpSlotData::new(1, "test-hotp", HOTP_SECRET, OtpMode::SixDigits); assert_ok!((), admin.write_hotp_slot(slot_data, counter.into())); } -fn check_hotp_codes(device: &GenerateOtp, offset: u8) { +fn check_hotp_codes(device: &mut GenerateOtp, offset: u8) { HOTP_CODES.iter().enumerate().for_each(|(i, code)| { if i >= offset as usize { assert_ok!(code.to_string(), device.get_hotp_code(1)); @@ -63,6 +63,7 @@ fn check_hotp_codes(device: &GenerateOtp, offset: u8) { #[test_device] fn set_time(device: DeviceWrapper) { + let mut device = device; assert_ok!((), device.set_time(1546385382, true)); assert_ok!((), device.set_time(1546385392, false)); assert_cmd_err!(CommandError::Timestamp, device.set_time(1546385292, false)); @@ -71,36 +72,36 @@ fn set_time(device: DeviceWrapper) { #[test_device] fn hotp_no_pin(device: DeviceWrapper) { - let admin = make_admin_test_device(device); + let mut admin = make_admin_test_device(device); let config = Config::new(None, None, None, false); assert_ok!((), admin.write_config(config)); - configure_hotp(&admin, 0); - check_hotp_codes(admin.deref(), 0); + configure_hotp(&mut admin, 0); + check_hotp_codes(admin.deref_mut(), 0); - configure_hotp(&admin, 5); - check_hotp_codes(admin.deref(), 5); + configure_hotp(&mut admin, 5); + check_hotp_codes(admin.deref_mut(), 5); - configure_hotp(&admin, 0); - check_hotp_codes(&admin.device(), 0); + configure_hotp(&mut admin, 0); + check_hotp_codes(&mut admin.device(), 0); } #[test_device] fn hotp_pin(device: DeviceWrapper) { - let admin = make_admin_test_device(device); + let mut admin = make_admin_test_device(device); let config = Config::new(None, None, None, true); assert_ok!((), admin.write_config(config)); - configure_hotp(&admin, 0); - let user = unwrap_ok!(admin.device().authenticate_user(USER_PASSWORD)); - check_hotp_codes(&user, 0); + configure_hotp(&mut admin, 0); + let mut user = unwrap_ok!(admin.device().authenticate_user(USER_PASSWORD)); + check_hotp_codes(&mut user, 0); assert_cmd_err!(CommandError::NotAuthorized, user.device().get_hotp_code(1)); } #[test_device] fn hotp_slot_name(device: DeviceWrapper) { - let admin = make_admin_test_device(device); + let mut admin = make_admin_test_device(device); let slot_data = OtpSlotData::new(1, "test-hotp", HOTP_SECRET, OtpMode::SixDigits); assert_ok!((), admin.write_hotp_slot(slot_data, 0)); @@ -111,7 +112,7 @@ fn hotp_slot_name(device: DeviceWrapper) { #[test_device] fn hotp_error(device: DeviceWrapper) { - let admin = make_admin_test_device(device); + let mut admin = make_admin_test_device(device); let slot_data = OtpSlotData::new(1, "", HOTP_SECRET, OtpMode::SixDigits); assert_cmd_err!(CommandError::NoName, admin.write_hotp_slot(slot_data, 0)); let slot_data = OtpSlotData::new(4, "test", HOTP_SECRET, OtpMode::SixDigits); @@ -130,7 +131,7 @@ fn hotp_error(device: DeviceWrapper) { #[test_device] fn hotp_erase(device: DeviceWrapper) { - let admin = make_admin_test_device(device); + let mut admin = make_admin_test_device(device); let config = Config::new(None, None, None, false); assert_ok!((), admin.write_config(config)); let slot_data = OtpSlotData::new(1, "test1", HOTP_SECRET, OtpMode::SixDigits); @@ -140,7 +141,7 @@ fn hotp_erase(device: DeviceWrapper) { assert_ok!((), admin.erase_hotp_slot(1)); - let device = admin.device(); + let mut device = admin.device(); let result = device.get_hotp_slot_name(1); assert_cmd_err!(CommandError::SlotNotProgrammed, result); let result = device.get_hotp_code(1); @@ -149,13 +150,13 @@ fn hotp_erase(device: DeviceWrapper) { assert_ok!("test2".to_string(), device.get_hotp_slot_name(2)); } -fn configure_totp(admin: &ConfigureOtp, factor: u64) { +fn configure_totp(admin: &mut ConfigureOtp, factor: u64) { let slot_data = OtpSlotData::new(1, "test-totp", TOTP_SECRET, OtpMode::EightDigits); let time_window = 30u64.checked_mul(factor).unwrap(); assert_ok!((), admin.write_totp_slot(slot_data, time_window as u16)); } -fn check_totp_codes(device: &GenerateOtp, factor: u64, timestamp_size: TotpTimestampSize) { +fn check_totp_codes(device: &mut GenerateOtp, factor: u64, timestamp_size: TotpTimestampSize) { for (base_time, codes) in TOTP_CODES { let time = base_time.checked_mul(factor).unwrap(); let is_u64 = time > u32::max_value() as u64; @@ -177,49 +178,47 @@ fn check_totp_codes(device: &GenerateOtp, factor: u64, timestamp_size: TotpTimes #[test_device] fn totp_no_pin(device: DeviceWrapper) { - // TODO: this test may fail due to bad timing --> find solution - let admin = make_admin_test_device(device); + let mut admin = make_admin_test_device(device); let config = Config::new(None, None, None, false); assert_ok!((), admin.write_config(config)); - configure_totp(&admin, 1); - check_totp_codes(admin.deref(), 1, TotpTimestampSize::U32); + configure_totp(&mut admin, 1); + check_totp_codes(admin.deref_mut(), 1, TotpTimestampSize::U32); - configure_totp(&admin, 2); - check_totp_codes(admin.deref(), 2, TotpTimestampSize::U32); + configure_totp(&mut admin, 2); + check_totp_codes(admin.deref_mut(), 2, TotpTimestampSize::U32); - configure_totp(&admin, 1); - check_totp_codes(&admin.device(), 1, TotpTimestampSize::U32); + configure_totp(&mut admin, 1); + check_totp_codes(&mut admin.device(), 1, TotpTimestampSize::U32); } #[test_device] // Nitrokey Storage does only support timestamps that fit in a 32-bit // unsigned integer, so don't test with it. fn totp_no_pin_64(device: Pro) { - let admin = make_admin_test_device(device); + let mut admin = make_admin_test_device(device); let config = Config::new(None, None, None, false); assert_ok!((), admin.write_config(config)); - configure_totp(&admin, 1); - check_totp_codes(admin.deref(), 1, TotpTimestampSize::U64); + configure_totp(&mut admin, 1); + check_totp_codes(admin.deref_mut(), 1, TotpTimestampSize::U64); - configure_totp(&admin, 2); - check_totp_codes(admin.deref(), 2, TotpTimestampSize::U64); + configure_totp(&mut admin, 2); + check_totp_codes(admin.deref_mut(), 2, TotpTimestampSize::U64); - configure_totp(&admin, 1); - check_totp_codes(&admin.device(), 1, TotpTimestampSize::U64); + configure_totp(&mut admin, 1); + check_totp_codes(&mut admin.device(), 1, TotpTimestampSize::U64); } #[test_device] fn totp_pin(device: DeviceWrapper) { - // TODO: this test may fail due to bad timing --> find solution - let admin = make_admin_test_device(device); + let mut admin = make_admin_test_device(device); let config = Config::new(None, None, None, true); assert_ok!((), admin.write_config(config)); - configure_totp(&admin, 1); - let user = unwrap_ok!(admin.device().authenticate_user(USER_PASSWORD)); - check_totp_codes(&user, 1, TotpTimestampSize::U32); + configure_totp(&mut admin, 1); + let mut user = unwrap_ok!(admin.device().authenticate_user(USER_PASSWORD)); + check_totp_codes(&mut user, 1, TotpTimestampSize::U32); assert_cmd_err!(CommandError::NotAuthorized, user.device().get_totp_code(1)); } @@ -227,20 +226,20 @@ fn totp_pin(device: DeviceWrapper) { #[test_device] // See comment for totp_no_pin_64. fn totp_pin_64(device: Pro) { - let admin = make_admin_test_device(device); + let mut admin = make_admin_test_device(device); let config = Config::new(None, None, None, true); assert_ok!((), admin.write_config(config)); - configure_totp(&admin, 1); - let user = unwrap_ok!(admin.device().authenticate_user(USER_PASSWORD)); - check_totp_codes(&user, 1, TotpTimestampSize::U64); + configure_totp(&mut admin, 1); + let mut user = unwrap_ok!(admin.device().authenticate_user(USER_PASSWORD)); + check_totp_codes(&mut user, 1, TotpTimestampSize::U64); assert_cmd_err!(CommandError::NotAuthorized, user.device().get_totp_code(1)); } #[test_device] fn totp_slot_name(device: DeviceWrapper) { - let admin = make_admin_test_device(device); + let mut admin = make_admin_test_device(device); let slot_data = OtpSlotData::new(1, "test-totp", TOTP_SECRET, OtpMode::EightDigits); assert_ok!((), admin.write_totp_slot(slot_data, 0)); @@ -253,7 +252,7 @@ fn totp_slot_name(device: DeviceWrapper) { #[test_device] fn totp_error(device: DeviceWrapper) { - let admin = make_admin_test_device(device); + let mut admin = make_admin_test_device(device); let slot_data = OtpSlotData::new(1, "", TOTP_SECRET, OtpMode::SixDigits); assert_cmd_err!(CommandError::NoName, admin.write_totp_slot(slot_data, 0)); let slot_data = OtpSlotData::new(20, "test", TOTP_SECRET, OtpMode::SixDigits); @@ -272,7 +271,7 @@ fn totp_error(device: DeviceWrapper) { #[test_device] fn totp_erase(device: DeviceWrapper) { - let admin = make_admin_test_device(device); + let mut admin = make_admin_test_device(device); let config = Config::new(None, None, None, false); assert_ok!((), admin.write_config(config)); let slot_data = OtpSlotData::new(1, "test1", TOTP_SECRET, OtpMode::SixDigits); diff --git a/tests/pws.rs b/tests/pws.rs index 32dc8f7..7805803 100644 --- a/tests/pws.rs +++ b/tests/pws.rs @@ -33,7 +33,7 @@ fn get_slot_name_direct(slot: u8) -> Result { } } -fn get_pws(device: &T) -> PasswordSafe +fn get_pws(device: &mut T) -> PasswordSafe where T: Device, { @@ -42,6 +42,7 @@ where #[test_device] fn enable(device: DeviceWrapper) { + let mut device = device; assert_cmd_err!( CommandError::WrongPassword, device.get_password_safe(&(USER_PASSWORD.to_owned() + "123")) @@ -56,8 +57,9 @@ fn enable(device: DeviceWrapper) { #[test_device] fn drop(device: DeviceWrapper) { + let mut device = device; { - let pws = get_pws(&device); + let mut pws = get_pws(&mut device); assert_ok!((), pws.write_slot(1, "name", "login", "password")); assert_ok!("name".to_string(), pws.get_slot_name(1)); let result = get_slot_name_direct(1); @@ -72,7 +74,8 @@ fn drop(device: DeviceWrapper) { #[test_device] fn get_status(device: DeviceWrapper) { - let pws = get_pws(&device); + let mut device = device; + let mut pws = get_pws(&mut device); for i in 0..SLOT_COUNT { assert_ok!((), pws.erase_slot(i)); } @@ -93,7 +96,8 @@ fn get_status(device: DeviceWrapper) { #[test_device] fn get_data(device: DeviceWrapper) { - let pws = get_pws(&device); + let mut device = device; + let mut pws = get_pws(&mut device); assert_ok!((), pws.write_slot(1, "name", "login", "password")); assert_ok!("name".to_string(), pws.get_slot_name(1)); assert_ok!("login".to_string(), pws.get_slot_login(1)); @@ -119,7 +123,8 @@ fn get_data(device: DeviceWrapper) { #[test_device] fn write(device: DeviceWrapper) { - let pws = get_pws(&device); + let mut device = device; + let mut pws = get_pws(&mut device); assert_lib_err!( LibraryError::InvalidSlot, @@ -144,7 +149,8 @@ fn write(device: DeviceWrapper) { #[test_device] fn erase(device: DeviceWrapper) { - let pws = get_pws(&device); + let mut device = device; + let mut pws = get_pws(&mut device); assert_lib_err!(LibraryError::InvalidSlot, pws.erase_slot(SLOT_COUNT)); assert_ok!((), pws.write_slot(0, "name", "login", "password")); -- cgit v1.2.3 From 0972bbe82623c3d9649b6023d8f50d304aa0cde6 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 28 Jan 2019 14:24:12 +0000 Subject: Refactor User and Admin to use a mutable reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the initial nitrokey-rs implementation, the Admin and the User struct take the Device by value to make sure that the user cannot initiate a second authentication while this first is still active (which would invalidate the temporary password). Now we realized that this is not necessary – taking a mutable reference has the same effect, but leads to a much cleaner API. This patch refactors the Admin and User structs – and all dependent code – to use a mutable reference instead of a Device value. --- CHANGELOG.md | 2 + src/auth.rs | 183 ++++++++++++++++-------------------------------------- src/device.rs | 42 ++++--------- src/lib.rs | 4 +- src/otp.rs | 16 ++--- tests/device.rs | 137 ++++++++++++++++++++-------------------- tests/otp.rs | 66 +++++++++++--------- tests/util/mod.rs | 12 ++-- 8 files changed, 185 insertions(+), 277 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index e98e857..8e6cb9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ SPDX-License-Identifier: MIT - Implement `DerefMut` for `User` and `Admin`. - Add `device_mut` method to `DeviceWrapper`. - Require a mutable `Device` reference if a method changes the device state. +- Let `Admin` and `User` store a mutable reference to the `Device` instead of + the `Device` value. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/auth.rs b/src/auth.rs index f9f50fa..573fed3 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -42,16 +42,10 @@ pub trait Authenticate { /// fn perform_other_task(device: &DeviceWrapper) {} /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; - /// let device = match device.authenticate_user("123456") { - /// Ok(user) => { - /// perform_user_task(&user); - /// user.device() - /// }, - /// Err((device, err)) => { - /// eprintln!("Could not authenticate as user: {}", err); - /// device - /// }, + /// let mut device = nitrokey::connect()?; + /// match device.authenticate_user("123456") { + /// Ok(user) => perform_user_task(&user), + /// Err(err) => eprintln!("Could not authenticate as user: {}", err), /// }; /// perform_other_task(&device); /// # Ok(()) @@ -61,9 +55,9 @@ pub trait Authenticate { /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`RngError`]: enum.CommandError.html#variant.RngError /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn authenticate_user(self, password: &str) -> Result, (Self, Error)> + fn authenticate_user(&mut self, password: &str) -> Result, Error> where - Self: Device + Sized; + Self: Device + std::marker::Sized; /// Performs admin authentication. This method consumes the device. If successful, an /// authenticated device is returned. Otherwise, the current unauthenticated device and the @@ -88,16 +82,10 @@ pub trait Authenticate { /// fn perform_other_task(device: &DeviceWrapper) {} /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; - /// let device = match device.authenticate_admin("123456") { - /// Ok(admin) => { - /// perform_admin_task(&admin); - /// admin.device() - /// }, - /// Err((device, err)) => { - /// eprintln!("Could not authenticate as admin: {}", err); - /// device - /// }, + /// let mut device = nitrokey::connect()?; + /// match device.authenticate_admin("123456") { + /// Ok(admin) => perform_admin_task(&admin), + /// Err(err) => eprintln!("Could not authenticate as admin: {}", err), /// }; /// perform_other_task(&device); /// # Ok(()) @@ -107,13 +95,13 @@ pub trait Authenticate { /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`RngError`]: enum.CommandError.html#variant.RngError /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn authenticate_admin(self, password: &str) -> Result, (Self, Error)> + fn authenticate_admin(&mut self, password: &str) -> Result, Error> where - Self: Device + Sized; + Self: Device + std::marker::Sized; } -trait AuthenticatedDevice { - fn new(device: T, temp_password: Vec) -> Self; +trait AuthenticatedDevice<'a, T> { + fn new(device: &'a mut T, temp_password: Vec) -> Self; fn temp_password_ptr(&self) -> *const c_char; } @@ -128,8 +116,8 @@ trait AuthenticatedDevice { /// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin /// [`device`]: #method.device #[derive(Debug)] -pub struct User { - device: T, +pub struct User<'a, T: Device> { + device: &'a mut T, temp_password: Vec, } @@ -143,89 +131,42 @@ pub struct User { /// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin /// [`device`]: #method.device #[derive(Debug)] -pub struct Admin { - device: T, +pub struct Admin<'a, T: Device> { + device: &'a mut T, temp_password: Vec, } -fn authenticate(device: D, password: &str, callback: T) -> Result +fn authenticate<'a, D, A, T>(device: &'a mut D, password: &str, callback: T) -> Result where D: Device, - A: AuthenticatedDevice, + A: AuthenticatedDevice<'a, D>, T: Fn(*const c_char, *const c_char) -> c_int, { - 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) { - Ok(password) => password, - Err(err) => return Err((device, err)), - }; + let temp_password = generate_password(TEMPORARY_PASSWORD_LENGTH)?; + let password = get_cstring(password)?; let password_ptr = password.as_ptr(); let temp_password_ptr = temp_password.as_ptr() as *const c_char; match callback(password_ptr, temp_password_ptr) { 0 => Ok(A::new(device, temp_password)), - rv => Err((device, Error::from(rv))), + rv => Err(Error::from(rv)), } } -fn authenticate_user_wrapper( - device: T, - constructor: C, - password: &str, -) -> Result, (DeviceWrapper, Error)> -where - T: Device, - C: Fn(T) -> DeviceWrapper, -{ - let result = device.authenticate_user(password); - match result { - Ok(user) => Ok(User::new(constructor(user.device), user.temp_password)), - Err((device, err)) => Err((constructor(device), err)), - } -} - -fn authenticate_admin_wrapper( - device: T, - constructor: C, - password: &str, -) -> Result, (DeviceWrapper, Error)> -where - T: Device, - C: Fn(T) -> DeviceWrapper, -{ - let result = device.authenticate_admin(password); - match result { - Ok(user) => Ok(Admin::new(constructor(user.device), user.temp_password)), - Err((device, err)) => Err((constructor(device), err)), - } -} - -impl User { - /// Forgets the user authentication and returns an unauthenticated device. This method - /// consumes the authenticated device. It does not perform any actual commands on the - /// Nitrokey. - pub fn device(self) -> T { - self.device - } -} - -impl ops::Deref for User { +impl<'a, T: Device> ops::Deref for User<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { - &self.device + self.device } } -impl ops::DerefMut for User { +impl<'a, T: Device> ops::DerefMut for User<'a, T> { fn deref_mut(&mut self) -> &mut T { - &mut self.device + self.device } } -impl GenerateOtp for User { +impl<'a, T: Device> GenerateOtp for User<'a, T> { fn get_hotp_code(&mut self, slot: u8) -> Result { result_from_string(unsafe { nitrokey_sys::NK_get_hotp_code_PIN(slot, self.temp_password_ptr()) @@ -239,8 +180,8 @@ impl GenerateOtp for User { } } -impl AuthenticatedDevice for User { - fn new(device: T, temp_password: Vec) -> Self { +impl<'a, T: Device> AuthenticatedDevice<'a, T> for User<'a, T> { + fn new(device: &'a mut T, temp_password: Vec) -> Self { User { device, temp_password, @@ -252,28 +193,21 @@ impl AuthenticatedDevice for User { } } -impl ops::Deref for Admin { +impl<'a, T: Device> ops::Deref for Admin<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { - &self.device + self.device } } -impl ops::DerefMut for Admin { +impl<'a, T: Device> ops::DerefMut for Admin<'a, T> { fn deref_mut(&mut self) -> &mut T { - &mut self.device - } -} - -impl Admin { - /// Forgets the user authentication and returns an unauthenticated device. This method - /// consumes the authenticated device. It does not perform any actual commands on the - /// Nitrokey. - pub fn device(self) -> T { self.device } +} +impl<'a, T: Device> Admin<'a, T> { /// Writes the given configuration to the Nitrokey device. /// /// # Errors @@ -287,14 +221,11 @@ impl Admin { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// let config = Config::new(None, None, None, false); /// match device.authenticate_admin("12345678") { - /// Ok(mut admin) => { - /// admin.write_config(config); - /// () - /// }, - /// Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err), + /// Ok(mut admin) => admin.write_config(config)?, + /// Err(err) => eprintln!("Could not authenticate as admin: {}", err), /// }; /// # Ok(()) /// # } @@ -316,7 +247,7 @@ impl Admin { } } -impl ConfigureOtp for Admin { +impl<'a, T: Device> ConfigureOtp for Admin<'a, T> { fn write_hotp_slot(&mut self, data: OtpSlotData, counter: u64) -> Result<(), Error> { let raw_data = RawOtpSlotData::new(data)?; get_command_result(unsafe { @@ -364,8 +295,8 @@ impl ConfigureOtp for Admin { } } -impl AuthenticatedDevice for Admin { - fn new(device: T, temp_password: Vec) -> Self { +impl<'a, T: Device> AuthenticatedDevice<'a, T> for Admin<'a, T> { + fn new(device: &'a mut T, temp_password: Vec) -> Self { Admin { device, temp_password, @@ -378,35 +309,27 @@ impl AuthenticatedDevice for Admin { } impl Authenticate for DeviceWrapper { - fn authenticate_user(self, password: &str) -> Result, (Self, Error)> { - match self { - DeviceWrapper::Storage(storage) => { - authenticate_user_wrapper(storage, DeviceWrapper::Storage, password) - } - DeviceWrapper::Pro(pro) => authenticate_user_wrapper(pro, DeviceWrapper::Pro, password), - } + fn authenticate_user(&mut self, password: &str) -> Result, 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, (Self, Error)> { - match self { - DeviceWrapper::Storage(storage) => { - authenticate_admin_wrapper(storage, DeviceWrapper::Storage, password) - } - DeviceWrapper::Pro(pro) => { - authenticate_admin_wrapper(pro, DeviceWrapper::Pro, password) - } - } + fn authenticate_admin(&mut self, password: &str) -> Result, Error> { + authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { + nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr) + }) } } impl Authenticate for Pro { - fn authenticate_user(self, password: &str) -> Result, (Self, Error)> { + fn authenticate_user(&mut self, password: &str) -> Result, 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, (Self, Error)> { + fn authenticate_admin(&mut self, password: &str) -> Result, Error> { authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr) }) @@ -414,13 +337,13 @@ impl Authenticate for Pro { } impl Authenticate for Storage { - fn authenticate_user(self, password: &str) -> Result, (Self, Error)> { + fn authenticate_user(&mut self, password: &str) -> Result, 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, (Self, Error)> { + fn authenticate_admin(&mut self, password: &str) -> Result, Error> { authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr) }) diff --git a/src/device.rs b/src/device.rs index f6492cd..a0df30e 100644 --- a/src/device.rs +++ b/src/device.rs @@ -71,16 +71,10 @@ impl fmt::Display for VolumeMode { /// fn perform_other_task(device: &DeviceWrapper) {} /// /// # fn try_main() -> Result<(), Error> { -/// let device = nitrokey::connect()?; -/// let device = match device.authenticate_user("123456") { -/// Ok(user) => { -/// perform_user_task(&user); -/// user.device() -/// }, -/// Err((device, err)) => { -/// eprintln!("Could not authenticate as user: {}", err); -/// device -/// }, +/// let mut device = nitrokey::connect()?; +/// match device.authenticate_user("123456") { +/// Ok(user) => perform_user_task(&user), +/// Err(err) => eprintln!("Could not authenticate as user: {}", err), /// }; /// perform_other_task(&device); /// # Ok(()) @@ -135,16 +129,10 @@ pub enum DeviceWrapper { /// fn perform_other_task(device: &Pro) {} /// /// # fn try_main() -> Result<(), Error> { -/// let device = nitrokey::Pro::connect()?; -/// let device = match device.authenticate_user("123456") { -/// Ok(user) => { -/// perform_user_task(&user); -/// user.device() -/// }, -/// Err((device, err)) => { -/// eprintln!("Could not authenticate as user: {}", err); -/// device -/// }, +/// let mut device = nitrokey::Pro::connect()?; +/// match device.authenticate_user("123456") { +/// Ok(user) => perform_user_task(&user), +/// Err(err) => eprintln!("Could not authenticate as user: {}", err), /// }; /// perform_other_task(&device); /// # Ok(()) @@ -181,16 +169,10 @@ pub struct Pro { /// fn perform_other_task(device: &Storage) {} /// /// # fn try_main() -> Result<(), Error> { -/// let device = nitrokey::Storage::connect()?; -/// let device = match device.authenticate_user("123456") { -/// Ok(user) => { -/// perform_user_task(&user); -/// user.device() -/// }, -/// Err((device, err)) => { -/// eprintln!("Could not authenticate as user: {}", err); -/// device -/// }, +/// let mut device = nitrokey::Storage::connect()?; +/// match device.authenticate_user("123456") { +/// Ok(user) => perform_user_task(&user), +/// Err(err) => eprintln!("Could not authenticate as user: {}", err), /// }; /// perform_other_task(&device); /// # Ok(()) diff --git a/src/lib.rs b/src/lib.rs index c35829c..d7a8c5e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,7 +44,7 @@ //! # use nitrokey::Error; //! //! # fn try_main() -> Result<(), Error> { -//! let device = nitrokey::connect()?; +//! let mut device = nitrokey::connect()?; //! let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits); //! match device.authenticate_admin("12345678") { //! Ok(mut admin) => { @@ -53,7 +53,7 @@ //! Err(err) => eprintln!("Could not write slot: {}", err), //! } //! }, -//! Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err), +//! Err(err) => eprintln!("Could not authenticate as admin: {}", err), //! } //! # Ok(()) //! # } diff --git a/src/otp.rs b/src/otp.rs index ee142c7..a8dd20b 100644 --- a/src/otp.rs +++ b/src/otp.rs @@ -35,7 +35,7 @@ pub trait ConfigureOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits); /// match device.authenticate_admin("12345678") { /// Ok(mut admin) => { @@ -44,7 +44,7 @@ pub trait ConfigureOtp { /// Err(err) => eprintln!("Could not write slot: {}", err), /// } /// }, - /// Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err), + /// Err(err) => eprintln!("Could not authenticate as admin: {}", err), /// } /// # Ok(()) /// # } @@ -71,7 +71,7 @@ pub trait ConfigureOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::EightDigits); /// match device.authenticate_admin("12345678") { /// Ok(mut admin) => { @@ -80,7 +80,7 @@ pub trait ConfigureOtp { /// Err(err) => eprintln!("Could not write slot: {}", err), /// } /// }, - /// Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err), + /// Err(err) => eprintln!("Could not authenticate as admin: {}", err), /// } /// # Ok(()) /// # } @@ -104,7 +104,7 @@ pub trait ConfigureOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// match device.authenticate_admin("12345678") { /// Ok(mut admin) => { /// match admin.erase_hotp_slot(1) { @@ -112,7 +112,7 @@ pub trait ConfigureOtp { /// Err(err) => eprintln!("Could not erase slot: {}", err), /// } /// }, - /// Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err), + /// Err(err) => eprintln!("Could not authenticate as admin: {}", err), /// } /// # Ok(()) /// # } @@ -134,7 +134,7 @@ pub trait ConfigureOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut device = nitrokey::connect()?; /// match device.authenticate_admin("12345678") { /// Ok(mut admin) => { /// match admin.erase_totp_slot(1) { @@ -142,7 +142,7 @@ pub trait ConfigureOtp { /// Err(err) => eprintln!("Could not erase slot: {}", err), /// } /// }, - /// Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err), + /// Err(err) => eprintln!("Could not authenticate as admin: {}", err), /// } /// # Ok(()) /// # } diff --git a/tests/device.rs b/tests/device.rs index 7a69214..bffd767 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -98,41 +98,34 @@ fn get_firmware_version(device: Pro) { assert!(version.minor > 0); } -fn admin_retry(device: T, suffix: &str, count: u8) -> T { - let result = device.authenticate_admin(&(ADMIN_PASSWORD.to_owned() + suffix)); - let device = match result { - Ok(admin) => admin.device(), - Err((device, _)) => device, - }; +fn admin_retry(device: &mut T, suffix: &str, count: u8) { + assert_any_ok!(device.authenticate_admin(&(ADMIN_PASSWORD.to_owned() + suffix))); assert_ok!(count, device.get_admin_retry_count()); - return device; } -fn user_retry(device: T, suffix: &str, count: u8) -> T { - let result = device.authenticate_user(&(USER_PASSWORD.to_owned() + suffix)); - let device = match result { - Ok(admin) => admin.device(), - Err((device, _)) => device, - }; +fn user_retry(device: &mut T, suffix: &str, count: u8) { + assert_any_ok!(device.authenticate_user(&(USER_PASSWORD.to_owned() + suffix))); assert_ok!(count, device.get_user_retry_count()); - return device; } #[test_device] fn get_retry_count(device: DeviceWrapper) { - let device = admin_retry(device, "", 3); - let device = admin_retry(device, "123", 2); - let device = admin_retry(device, "456", 1); - let device = admin_retry(device, "", 3); - - let device = user_retry(device, "", 3); - let device = user_retry(device, "123", 2); - let device = user_retry(device, "456", 1); - user_retry(device, "", 3); + let mut device = device; + + admin_retry(&mut device, "", 3); + admin_retry(&mut device, "123", 2); + admin_retry(&mut device, "456", 1); + admin_retry(&mut device, "", 3); + + user_retry(&mut device, "", 3); + user_retry(&mut device, "123", 2); + user_retry(&mut device, "456", 1); + user_retry(&mut device, "", 3); } #[test_device] fn config(device: DeviceWrapper) { + let mut device = device; let mut admin = unwrap_ok!(device.authenticate_admin(ADMIN_PASSWORD)); let config = Config::new(None, None, None, true); @@ -153,43 +146,52 @@ fn config(device: DeviceWrapper) { #[test_device] fn change_user_pin(device: DeviceWrapper) { - let device = device.authenticate_user(USER_PASSWORD).unwrap().device(); - let device = device.authenticate_user(USER_NEW_PASSWORD).unwrap_err().0; - let mut device = device; + assert_any_ok!(device.authenticate_user(USER_PASSWORD)); + assert_cmd_err!( + CommandError::WrongPassword, + device.authenticate_user(USER_NEW_PASSWORD) + ); + assert_ok!((), device.change_user_pin(USER_PASSWORD, USER_NEW_PASSWORD)); - let device = device.authenticate_user(USER_PASSWORD).unwrap_err().0; - let device = device - .authenticate_user(USER_NEW_PASSWORD) - .unwrap() - .device(); + assert_cmd_err!( + CommandError::WrongPassword, + device.authenticate_user(USER_PASSWORD) + ); + assert_any_ok!(device.authenticate_user(USER_NEW_PASSWORD)); - let mut device = device; let result = device.change_user_pin(USER_PASSWORD, USER_PASSWORD); assert_cmd_err!(CommandError::WrongPassword, result); assert_ok!((), device.change_user_pin(USER_NEW_PASSWORD, USER_PASSWORD)); - let device = device.authenticate_user(USER_PASSWORD).unwrap().device(); - assert!(device.authenticate_user(USER_NEW_PASSWORD).is_err()); + assert_any_ok!(device.authenticate_user(USER_PASSWORD)); + assert_cmd_err!( + CommandError::WrongPassword, + device.authenticate_user(USER_NEW_PASSWORD) + ); } #[test_device] fn change_admin_pin(device: DeviceWrapper) { - let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device(); - let mut device = device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err().0; + let mut device = device; + assert_any_ok!(device.authenticate_admin(ADMIN_PASSWORD)); + assert_cmd_err!( + CommandError::WrongPassword, + device.authenticate_admin(ADMIN_NEW_PASSWORD) + ); assert_ok!( (), device.change_admin_pin(ADMIN_PASSWORD, ADMIN_NEW_PASSWORD) ); - let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap_err().0; - let mut device = device - .authenticate_admin(ADMIN_NEW_PASSWORD) - .unwrap() - .device(); + assert_cmd_err!( + CommandError::WrongPassword, + device.authenticate_admin(ADMIN_PASSWORD) + ); + assert_any_ok!(device.authenticate_admin(ADMIN_NEW_PASSWORD)); assert_cmd_err!( CommandError::WrongPassword, @@ -201,28 +203,24 @@ fn change_admin_pin(device: DeviceWrapper) { device.change_admin_pin(ADMIN_NEW_PASSWORD, ADMIN_PASSWORD) ); - let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device(); - device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err(); + assert_any_ok!(device.authenticate_admin(ADMIN_PASSWORD)); + assert_cmd_err!( + CommandError::WrongPassword, + device.authenticate_admin(ADMIN_NEW_PASSWORD) + ); } -fn require_failed_user_login(device: D, password: &str, error: CommandError) -> D -where - D: Device + Authenticate, - nitrokey::User: std::fmt::Debug, -{ - let result = device.authenticate_user(password); - assert!(result.is_err()); - let err = result.unwrap_err(); - match err.1 { - Error::CommandError(err) => assert_eq!(error, err), - _ => assert!(false), - }; - err.0 +fn require_failed_user_login(device: &mut D, password: &str) { + assert_cmd_err!( + CommandError::WrongPassword, + device.authenticate_user(password) + ); } #[test_device] fn unlock_user_pin(device: DeviceWrapper) { - let mut device = device.authenticate_user(USER_PASSWORD).unwrap().device(); + let mut device = device; + assert_any_ok!(device.authenticate_user(USER_PASSWORD)); assert_ok!((), device.unlock_user_pin(ADMIN_PASSWORD, USER_PASSWORD)); assert_cmd_err!( CommandError::WrongPassword, @@ -231,10 +229,10 @@ fn unlock_user_pin(device: DeviceWrapper) { // block user PIN let wrong_password = USER_PASSWORD.to_owned() + "foo"; - let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); - let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); - let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); - let mut device = require_failed_user_login(device, USER_PASSWORD, CommandError::WrongPassword); + require_failed_user_login(&mut device, &wrong_password); + require_failed_user_login(&mut device, &wrong_password); + require_failed_user_login(&mut device, &wrong_password); + require_failed_user_login(&mut device, USER_PASSWORD); // unblock with current PIN assert_cmd_err!( @@ -242,13 +240,13 @@ fn unlock_user_pin(device: DeviceWrapper) { device.unlock_user_pin(USER_PASSWORD, USER_PASSWORD) ); assert_ok!((), device.unlock_user_pin(ADMIN_PASSWORD, USER_PASSWORD)); - let device = device.authenticate_user(USER_PASSWORD).unwrap().device(); + assert_any_ok!(device.authenticate_user(USER_PASSWORD)); // block user PIN - let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); - let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); - let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); - let mut device = require_failed_user_login(device, USER_PASSWORD, CommandError::WrongPassword); + require_failed_user_login(&mut device, &wrong_password); + require_failed_user_login(&mut device, &wrong_password); + require_failed_user_login(&mut device, &wrong_password); + require_failed_user_login(&mut device, USER_PASSWORD); // unblock with new PIN assert_cmd_err!( @@ -274,11 +272,11 @@ fn assert_utf8_err_or_ne(left: &str, right: Result) { #[test_device] fn factory_reset(device: DeviceWrapper) { + let mut device = device; let mut admin = unwrap_ok!(device.authenticate_admin(ADMIN_PASSWORD)); let otp_data = OtpSlotData::new(1, "test", "0123468790", OtpMode::SixDigits); assert_ok!((), admin.write_totp_slot(otp_data, 30)); - let mut device = admin.device(); let mut pws = unwrap_ok!(device.get_password_safe(USER_PASSWORD)); assert_ok!((), pws.write_slot(0, "test", "testlogin", "testpw")); drop(pws); @@ -299,12 +297,11 @@ fn factory_reset(device: DeviceWrapper) { ); assert_ok!((), device.factory_reset(ADMIN_NEW_PASSWORD)); - let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device(); + assert_any_ok!(device.authenticate_admin(ADMIN_PASSWORD)); let user = unwrap_ok!(device.authenticate_user(USER_PASSWORD)); assert_cmd_err!(CommandError::SlotNotProgrammed, user.get_totp_slot_name(1)); - let mut device = user.device(); let pws = unwrap_ok!(device.get_password_safe(USER_PASSWORD)); assert_utf8_err_or_ne("test", pws.get_slot_name(0)); assert_utf8_err_or_ne("testlogin", pws.get_slot_login(0)); @@ -327,7 +324,7 @@ fn build_aes_key(device: DeviceWrapper) { ); assert_ok!((), device.build_aes_key(ADMIN_PASSWORD)); - let mut device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device(); + assert_any_ok!(device.authenticate_admin(ADMIN_PASSWORD)); let pws = unwrap_ok!(device.get_password_safe(USER_PASSWORD)); assert_utf8_err_or_ne("test", pws.get_slot_name(0)); diff --git a/tests/otp.rs b/tests/otp.rs index 28a8d7c..8ca8311 100644 --- a/tests/otp.rs +++ b/tests/otp.rs @@ -3,7 +3,6 @@ mod util; -use std::fmt::Debug; use std::ops::DerefMut; use nitrokey::{ @@ -38,14 +37,11 @@ enum TotpTimestampSize { U64, } -fn make_admin_test_device(device: T) -> Admin +fn make_admin_test_device<'a, T>(device: &'a mut T) -> Admin<'a, T> where T: Device, - (T, nitrokey::Error): Debug, { - device - .authenticate_admin(ADMIN_PASSWORD) - .expect("Could not login as admin.") + unwrap_ok!(device.authenticate_admin(ADMIN_PASSWORD)) } fn configure_hotp(admin: &mut ConfigureOtp, counter: u8) { @@ -72,7 +68,8 @@ fn set_time(device: DeviceWrapper) { #[test_device] fn hotp_no_pin(device: DeviceWrapper) { - let mut admin = make_admin_test_device(device); + let mut device = device; + let mut admin = make_admin_test_device(&mut device); let config = Config::new(None, None, None, false); assert_ok!((), admin.write_config(config)); @@ -83,36 +80,38 @@ fn hotp_no_pin(device: DeviceWrapper) { check_hotp_codes(admin.deref_mut(), 5); configure_hotp(&mut admin, 0); - check_hotp_codes(&mut admin.device(), 0); + check_hotp_codes(&mut device, 0); } #[test_device] fn hotp_pin(device: DeviceWrapper) { - let mut admin = make_admin_test_device(device); + let mut device = device; + let mut admin = make_admin_test_device(&mut device); let config = Config::new(None, None, None, true); assert_ok!((), admin.write_config(config)); configure_hotp(&mut admin, 0); - let mut user = unwrap_ok!(admin.device().authenticate_user(USER_PASSWORD)); + let mut user = unwrap_ok!(device.authenticate_user(USER_PASSWORD)); check_hotp_codes(&mut user, 0); - assert_cmd_err!(CommandError::NotAuthorized, user.device().get_hotp_code(1)); + assert_cmd_err!(CommandError::NotAuthorized, user.get_hotp_code(1)); } #[test_device] fn hotp_slot_name(device: DeviceWrapper) { - let mut admin = make_admin_test_device(device); + let mut device = device; + let mut admin = make_admin_test_device(&mut device); let slot_data = OtpSlotData::new(1, "test-hotp", HOTP_SECRET, OtpMode::SixDigits); assert_ok!((), admin.write_hotp_slot(slot_data, 0)); - let device = admin.device(); assert_ok!("test-hotp".to_string(), device.get_hotp_slot_name(1)); assert_lib_err!(LibraryError::InvalidSlot, device.get_hotp_slot_name(4)); } #[test_device] fn hotp_error(device: DeviceWrapper) { - let mut admin = make_admin_test_device(device); + let mut device = device; + let mut admin = make_admin_test_device(&mut device); let slot_data = OtpSlotData::new(1, "", HOTP_SECRET, OtpMode::SixDigits); assert_cmd_err!(CommandError::NoName, admin.write_hotp_slot(slot_data, 0)); let slot_data = OtpSlotData::new(4, "test", HOTP_SECRET, OtpMode::SixDigits); @@ -131,7 +130,8 @@ fn hotp_error(device: DeviceWrapper) { #[test_device] fn hotp_erase(device: DeviceWrapper) { - let mut admin = make_admin_test_device(device); + let mut device = device; + let mut admin = make_admin_test_device(&mut device); let config = Config::new(None, None, None, false); assert_ok!((), admin.write_config(config)); let slot_data = OtpSlotData::new(1, "test1", HOTP_SECRET, OtpMode::SixDigits); @@ -141,7 +141,6 @@ fn hotp_erase(device: DeviceWrapper) { assert_ok!((), admin.erase_hotp_slot(1)); - let mut device = admin.device(); let result = device.get_hotp_slot_name(1); assert_cmd_err!(CommandError::SlotNotProgrammed, result); let result = device.get_hotp_code(1); @@ -178,7 +177,8 @@ fn check_totp_codes(device: &mut GenerateOtp, factor: u64, timestamp_size: TotpT #[test_device] fn totp_no_pin(device: DeviceWrapper) { - let mut admin = make_admin_test_device(device); + let mut device = device; + let mut admin = make_admin_test_device(&mut device); let config = Config::new(None, None, None, false); assert_ok!((), admin.write_config(config)); @@ -189,14 +189,15 @@ fn totp_no_pin(device: DeviceWrapper) { check_totp_codes(admin.deref_mut(), 2, TotpTimestampSize::U32); configure_totp(&mut admin, 1); - check_totp_codes(&mut admin.device(), 1, TotpTimestampSize::U32); + check_totp_codes(&mut device, 1, TotpTimestampSize::U32); } #[test_device] // Nitrokey Storage does only support timestamps that fit in a 32-bit // unsigned integer, so don't test with it. fn totp_no_pin_64(device: Pro) { - let mut admin = make_admin_test_device(device); + let mut device = device; + let mut admin = make_admin_test_device(&mut device); let config = Config::new(None, None, None, false); assert_ok!((), admin.write_config(config)); @@ -207,43 +208,45 @@ fn totp_no_pin_64(device: Pro) { check_totp_codes(admin.deref_mut(), 2, TotpTimestampSize::U64); configure_totp(&mut admin, 1); - check_totp_codes(&mut admin.device(), 1, TotpTimestampSize::U64); + check_totp_codes(&mut device, 1, TotpTimestampSize::U64); } #[test_device] fn totp_pin(device: DeviceWrapper) { - let mut admin = make_admin_test_device(device); + let mut device = device; + let mut admin = make_admin_test_device(&mut device); let config = Config::new(None, None, None, true); assert_ok!((), admin.write_config(config)); configure_totp(&mut admin, 1); - let mut user = unwrap_ok!(admin.device().authenticate_user(USER_PASSWORD)); + let mut user = unwrap_ok!(device.authenticate_user(USER_PASSWORD)); check_totp_codes(&mut user, 1, TotpTimestampSize::U32); - assert_cmd_err!(CommandError::NotAuthorized, user.device().get_totp_code(1)); + assert_cmd_err!(CommandError::NotAuthorized, user.get_totp_code(1)); } #[test_device] // See comment for totp_no_pin_64. fn totp_pin_64(device: Pro) { - let mut admin = make_admin_test_device(device); + let mut device = device; + let mut admin = make_admin_test_device(&mut device); let config = Config::new(None, None, None, true); assert_ok!((), admin.write_config(config)); configure_totp(&mut admin, 1); - let mut user = unwrap_ok!(admin.device().authenticate_user(USER_PASSWORD)); + let mut user = unwrap_ok!(admin.authenticate_user(USER_PASSWORD)); check_totp_codes(&mut user, 1, TotpTimestampSize::U64); - assert_cmd_err!(CommandError::NotAuthorized, user.device().get_totp_code(1)); + assert_cmd_err!(CommandError::NotAuthorized, device.get_totp_code(1)); } #[test_device] fn totp_slot_name(device: DeviceWrapper) { - let mut admin = make_admin_test_device(device); + let mut device = device; + let mut admin = make_admin_test_device(&mut device); let slot_data = OtpSlotData::new(1, "test-totp", TOTP_SECRET, OtpMode::EightDigits); assert_ok!((), admin.write_totp_slot(slot_data, 0)); - let device = admin.device(); let result = device.get_totp_slot_name(1); assert_ok!("test-totp", result); let result = device.get_totp_slot_name(16); @@ -252,7 +255,8 @@ fn totp_slot_name(device: DeviceWrapper) { #[test_device] fn totp_error(device: DeviceWrapper) { - let mut admin = make_admin_test_device(device); + let mut device = device; + let mut admin = make_admin_test_device(&mut device); let slot_data = OtpSlotData::new(1, "", TOTP_SECRET, OtpMode::SixDigits); assert_cmd_err!(CommandError::NoName, admin.write_totp_slot(slot_data, 0)); let slot_data = OtpSlotData::new(20, "test", TOTP_SECRET, OtpMode::SixDigits); @@ -271,7 +275,8 @@ fn totp_error(device: DeviceWrapper) { #[test_device] fn totp_erase(device: DeviceWrapper) { - let mut admin = make_admin_test_device(device); + let mut device = device; + let mut admin = make_admin_test_device(&mut device); let config = Config::new(None, None, None, false); assert_ok!((), admin.write_config(config)); let slot_data = OtpSlotData::new(1, "test1", TOTP_SECRET, OtpMode::SixDigits); @@ -281,7 +286,6 @@ fn totp_erase(device: DeviceWrapper) { assert_ok!((), admin.erase_totp_slot(1)); - let device = admin.device(); let result = device.get_totp_slot_name(1); assert_cmd_err!(CommandError::SlotNotProgrammed, result); let result = device.get_totp_code(1); diff --git a/tests/util/mod.rs b/tests/util/mod.rs index bd207a9..f80372d 100644 --- a/tests/util/mod.rs +++ b/tests/util/mod.rs @@ -8,7 +8,7 @@ pub static USER_PASSWORD: &str = "123456"; #[macro_export] macro_rules! unwrap_ok { - ($val:expr) => {{ + ($val:expr) => { match $val { Ok(val) => val, Err(err) => panic!( @@ -18,12 +18,12 @@ macro_rules! unwrap_ok { err ), } - }}; + }; } #[macro_export] macro_rules! assert_any_ok { - ($val:expr) => {{ + ($val:expr) => { match &$val { Ok(_) => {} Err(err) => panic!( @@ -33,12 +33,12 @@ macro_rules! assert_any_ok { err ), } - }}; + }; } #[macro_export] macro_rules! assert_ok { - ($left:expr, $right:expr) => {{ + ($left:expr, $right:expr) => { match &$right { Ok(right) => match &$left { left => { @@ -59,7 +59,7 @@ macro_rules! assert_ok { $left, right_err ), } - }}; + }; } #[macro_export] -- cgit v1.2.3 From 13006c00dcbd570cf8347d89557834e320427377 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 30 Jan 2019 18:40:11 +0000 Subject: Store mutable reference to Device in PasswordSafe The current implementation of PasswordSafe stored a normal reference to the Device. This patch changes the PasswordSafe struct to use a mutable reference instead. This allows the borrow checker to make sure that there is only one PasswordSafe instance at a time. While this is currently not needed, it will become important once we can lock the PWS on the Nitrokey when dropping the PasswordSafe instance. --- CHANGELOG.md | 2 ++ TODO.md | 1 - src/pws.rs | 24 ++++++++++++------------ 3 files changed, 14 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e6cb9c..9227510 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,8 @@ SPDX-License-Identifier: MIT - Require a mutable `Device` reference if a method changes the device state. - Let `Admin` and `User` store a mutable reference to the `Device` instead of the `Device` value. +- Let `PasswordStore` store a mutable reference to the `Device` instead of a + non-mutable reference. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/TODO.md b/TODO.md index 1ff723d..d6a3509 100644 --- a/TODO.md +++ b/TODO.md @@ -13,7 +13,6 @@ SPDX-License-Identifier: MIT - Clear passwords from memory. - Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware issue 65][]). -- Disable creation of multiple password safes at the same time. - Check timing in Storage tests. - Consider restructuring `device::StorageStatus`. diff --git a/src/pws.rs b/src/pws.rs index 371de6e..a5b9d33 100644 --- a/src/pws.rs +++ b/src/pws.rs @@ -18,8 +18,7 @@ pub const SLOT_COUNT: u8 = 16; /// The password safe stores a tuple consisting of a name, a login and a password on a slot. The /// number of available slots is [`SLOT_COUNT`][]. The slots are addressed starting with zero. To /// retrieve a password safe from a Nitrokey device, use the [`get_password_safe`][] method from -/// the [`GetPasswordSafe`][] trait. Note that the device must live at least as long as the -/// password safe. +/// the [`GetPasswordSafe`][] trait. /// /// Once the password safe has been unlocked, it can be accessed without a password. Therefore it /// is mandatory to call [`lock`][] on the corresponding device after the password store is used. @@ -58,21 +57,17 @@ pub const SLOT_COUNT: u8 = 16; /// [`GetPasswordSafe`]: trait.GetPasswordSafe.html #[derive(Debug)] pub struct PasswordSafe<'a> { - _device: &'a dyn Device, + device: &'a mut dyn Device, } /// Provides access to a [`PasswordSafe`][]. /// -/// The device that implements this trait must always live at least as long as a password safe -/// retrieved from it. -/// /// [`PasswordSafe`]: struct.PasswordSafe.html pub trait GetPasswordSafe { /// Enables and returns the password safe. /// - /// The underlying device must always live at least as long as a password safe retrieved from - /// it. It is mandatory to lock the underlying device using [`lock`][] after the password safe - /// has been used. Otherwise, other applications can access the password store without + /// It is mandatory to lock the underlying device using [`lock`][] after the password safe has + /// been used. Otherwise, other applications can access the password store without /// authentication. /// /// If this method returns an `AesDecryptionFailed` (Nitrokey Pro) or `Unknown` (Nitrokey @@ -121,12 +116,17 @@ pub trait GetPasswordSafe { } fn get_password_safe<'a>( - device: &'a dyn Device, + device: &'a mut dyn Device, user_pin: &str, ) -> Result, 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 result = get_command_result(unsafe { + nitrokey_sys::NK_enable_password_safe(user_pin_string.as_ptr()) + }); + match result { + Ok(()) => Ok(PasswordSafe { device }), + Err(err) => Err(err), + } } fn get_pws_result(s: String) -> Result { -- cgit v1.2.3 From 83641ca0518e4c766c63e40d0787e4f0b436652a Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 5 Feb 2019 12:47:24 +0000 Subject: Revert "Refactor User and Admin to use a mutable reference" This reverts commit 0972bbe82623c3d9649b6023d8f50d304aa0cde6. --- CHANGELOG.md | 4 -- src/auth.rs | 183 ++++++++++++++++++++++++++++++++++++++---------------- src/device.rs | 42 +++++++++---- src/lib.rs | 4 +- src/otp.rs | 16 ++--- tests/device.rs | 152 ++++++++++++++++++++++----------------------- tests/otp.rs | 62 +++++++++--------- tests/util/mod.rs | 12 ++-- 8 files changed, 277 insertions(+), 198 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 9227510..e98e857 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,10 +38,6 @@ SPDX-License-Identifier: MIT - Implement `DerefMut` for `User` and `Admin`. - Add `device_mut` method to `DeviceWrapper`. - Require a mutable `Device` reference if a method changes the device state. -- Let `Admin` and `User` store a mutable reference to the `Device` instead of - the `Device` value. -- Let `PasswordStore` store a mutable reference to the `Device` instead of a - non-mutable reference. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/auth.rs b/src/auth.rs index 573fed3..f9f50fa 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -42,10 +42,16 @@ pub trait Authenticate { /// fn perform_other_task(device: &DeviceWrapper) {} /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; - /// match device.authenticate_user("123456") { - /// Ok(user) => perform_user_task(&user), - /// Err(err) => eprintln!("Could not authenticate as user: {}", err), + /// let device = nitrokey::connect()?; + /// let device = match device.authenticate_user("123456") { + /// Ok(user) => { + /// perform_user_task(&user); + /// user.device() + /// }, + /// Err((device, err)) => { + /// eprintln!("Could not authenticate as user: {}", err); + /// device + /// }, /// }; /// perform_other_task(&device); /// # Ok(()) @@ -55,9 +61,9 @@ pub trait Authenticate { /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`RngError`]: enum.CommandError.html#variant.RngError /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn authenticate_user(&mut self, password: &str) -> Result, Error> + fn authenticate_user(self, password: &str) -> Result, (Self, Error)> where - Self: Device + std::marker::Sized; + Self: Device + Sized; /// Performs admin authentication. This method consumes the device. If successful, an /// authenticated device is returned. Otherwise, the current unauthenticated device and the @@ -82,10 +88,16 @@ pub trait Authenticate { /// fn perform_other_task(device: &DeviceWrapper) {} /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; - /// match device.authenticate_admin("123456") { - /// Ok(admin) => perform_admin_task(&admin), - /// Err(err) => eprintln!("Could not authenticate as admin: {}", err), + /// let device = nitrokey::connect()?; + /// let device = match device.authenticate_admin("123456") { + /// Ok(admin) => { + /// perform_admin_task(&admin); + /// admin.device() + /// }, + /// Err((device, err)) => { + /// eprintln!("Could not authenticate as admin: {}", err); + /// device + /// }, /// }; /// perform_other_task(&device); /// # Ok(()) @@ -95,13 +107,13 @@ pub trait Authenticate { /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`RngError`]: enum.CommandError.html#variant.RngError /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn authenticate_admin(&mut self, password: &str) -> Result, Error> + fn authenticate_admin(self, password: &str) -> Result, (Self, Error)> where - Self: Device + std::marker::Sized; + Self: Device + Sized; } -trait AuthenticatedDevice<'a, T> { - fn new(device: &'a mut T, temp_password: Vec) -> Self; +trait AuthenticatedDevice { + fn new(device: T, temp_password: Vec) -> Self; fn temp_password_ptr(&self) -> *const c_char; } @@ -116,8 +128,8 @@ trait AuthenticatedDevice<'a, T> { /// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin /// [`device`]: #method.device #[derive(Debug)] -pub struct User<'a, T: Device> { - device: &'a mut T, +pub struct User { + device: T, temp_password: Vec, } @@ -131,42 +143,89 @@ pub struct User<'a, T: Device> { /// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin /// [`device`]: #method.device #[derive(Debug)] -pub struct Admin<'a, T: Device> { - device: &'a mut T, +pub struct Admin { + device: T, temp_password: Vec, } -fn authenticate<'a, D, A, T>(device: &'a mut D, password: &str, callback: T) -> Result +fn authenticate(device: D, password: &str, callback: T) -> Result where D: Device, - A: AuthenticatedDevice<'a, D>, + A: AuthenticatedDevice, T: Fn(*const c_char, *const c_char) -> c_int, { - let temp_password = generate_password(TEMPORARY_PASSWORD_LENGTH)?; - let password = get_cstring(password)?; + 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) { + Ok(password) => password, + Err(err) => return Err((device, err)), + }; let password_ptr = password.as_ptr(); let temp_password_ptr = temp_password.as_ptr() as *const c_char; match callback(password_ptr, temp_password_ptr) { 0 => Ok(A::new(device, temp_password)), - rv => Err(Error::from(rv)), + rv => Err((device, Error::from(rv))), } } -impl<'a, T: Device> ops::Deref for User<'a, T> { +fn authenticate_user_wrapper( + device: T, + constructor: C, + password: &str, +) -> Result, (DeviceWrapper, Error)> +where + T: Device, + C: Fn(T) -> DeviceWrapper, +{ + let result = device.authenticate_user(password); + match result { + Ok(user) => Ok(User::new(constructor(user.device), user.temp_password)), + Err((device, err)) => Err((constructor(device), err)), + } +} + +fn authenticate_admin_wrapper( + device: T, + constructor: C, + password: &str, +) -> Result, (DeviceWrapper, Error)> +where + T: Device, + C: Fn(T) -> DeviceWrapper, +{ + let result = device.authenticate_admin(password); + match result { + Ok(user) => Ok(Admin::new(constructor(user.device), user.temp_password)), + Err((device, err)) => Err((constructor(device), err)), + } +} + +impl User { + /// Forgets the user authentication and returns an unauthenticated device. This method + /// consumes the authenticated device. It does not perform any actual commands on the + /// Nitrokey. + pub fn device(self) -> T { + self.device + } +} + +impl ops::Deref for User { type Target = T; fn deref(&self) -> &Self::Target { - self.device + &self.device } } -impl<'a, T: Device> ops::DerefMut for User<'a, T> { +impl ops::DerefMut for User { fn deref_mut(&mut self) -> &mut T { - self.device + &mut self.device } } -impl<'a, T: Device> GenerateOtp for User<'a, T> { +impl GenerateOtp for User { fn get_hotp_code(&mut self, slot: u8) -> Result { result_from_string(unsafe { nitrokey_sys::NK_get_hotp_code_PIN(slot, self.temp_password_ptr()) @@ -180,8 +239,8 @@ impl<'a, T: Device> GenerateOtp for User<'a, T> { } } -impl<'a, T: Device> AuthenticatedDevice<'a, T> for User<'a, T> { - fn new(device: &'a mut T, temp_password: Vec) -> Self { +impl AuthenticatedDevice for User { + fn new(device: T, temp_password: Vec) -> Self { User { device, temp_password, @@ -193,21 +252,28 @@ impl<'a, T: Device> AuthenticatedDevice<'a, T> for User<'a, T> { } } -impl<'a, T: Device> ops::Deref for Admin<'a, T> { +impl ops::Deref for Admin { type Target = T; fn deref(&self) -> &Self::Target { - self.device + &self.device } } -impl<'a, T: Device> ops::DerefMut for Admin<'a, T> { +impl ops::DerefMut for Admin { fn deref_mut(&mut self) -> &mut T { - self.device + &mut self.device } } -impl<'a, T: Device> Admin<'a, T> { +impl Admin { + /// Forgets the user authentication and returns an unauthenticated device. This method + /// consumes the authenticated device. It does not perform any actual commands on the + /// Nitrokey. + pub fn device(self) -> T { + self.device + } + /// Writes the given configuration to the Nitrokey device. /// /// # Errors @@ -221,11 +287,14 @@ impl<'a, T: Device> Admin<'a, T> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let device = nitrokey::connect()?; /// let config = Config::new(None, None, None, false); /// match device.authenticate_admin("12345678") { - /// Ok(mut admin) => admin.write_config(config)?, - /// Err(err) => eprintln!("Could not authenticate as admin: {}", err), + /// Ok(mut admin) => { + /// admin.write_config(config); + /// () + /// }, + /// Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err), /// }; /// # Ok(()) /// # } @@ -247,7 +316,7 @@ impl<'a, T: Device> Admin<'a, T> { } } -impl<'a, T: Device> ConfigureOtp for Admin<'a, T> { +impl ConfigureOtp for Admin { fn write_hotp_slot(&mut self, data: OtpSlotData, counter: u64) -> Result<(), Error> { let raw_data = RawOtpSlotData::new(data)?; get_command_result(unsafe { @@ -295,8 +364,8 @@ impl<'a, T: Device> ConfigureOtp for Admin<'a, T> { } } -impl<'a, T: Device> AuthenticatedDevice<'a, T> for Admin<'a, T> { - fn new(device: &'a mut T, temp_password: Vec) -> Self { +impl AuthenticatedDevice for Admin { + fn new(device: T, temp_password: Vec) -> Self { Admin { device, temp_password, @@ -309,27 +378,35 @@ impl<'a, T: Device> AuthenticatedDevice<'a, T> for Admin<'a, T> { } impl Authenticate for DeviceWrapper { - fn authenticate_user(&mut self, password: &str) -> Result, Error> { - authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { - nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr) - }) + fn authenticate_user(self, password: &str) -> Result, (Self, Error)> { + match self { + DeviceWrapper::Storage(storage) => { + authenticate_user_wrapper(storage, DeviceWrapper::Storage, password) + } + DeviceWrapper::Pro(pro) => authenticate_user_wrapper(pro, DeviceWrapper::Pro, password), + } } - fn authenticate_admin(&mut self, password: &str) -> Result, 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, (Self, Error)> { + match self { + DeviceWrapper::Storage(storage) => { + authenticate_admin_wrapper(storage, DeviceWrapper::Storage, password) + } + DeviceWrapper::Pro(pro) => { + authenticate_admin_wrapper(pro, DeviceWrapper::Pro, password) + } + } } } impl Authenticate for Pro { - fn authenticate_user(&mut self, password: &str) -> Result, Error> { + fn authenticate_user(self, password: &str) -> Result, (Self, Error)> { authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr) }) } - fn authenticate_admin(&mut self, password: &str) -> Result, Error> { + fn authenticate_admin(self, password: &str) -> Result, (Self, Error)> { authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr) }) @@ -337,13 +414,13 @@ impl Authenticate for Pro { } impl Authenticate for Storage { - fn authenticate_user(&mut self, password: &str) -> Result, Error> { + fn authenticate_user(self, password: &str) -> Result, (Self, Error)> { authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr) }) } - fn authenticate_admin(&mut self, password: &str) -> Result, Error> { + fn authenticate_admin(self, password: &str) -> Result, (Self, Error)> { authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr) }) diff --git a/src/device.rs b/src/device.rs index a0df30e..f6492cd 100644 --- a/src/device.rs +++ b/src/device.rs @@ -71,10 +71,16 @@ impl fmt::Display for VolumeMode { /// fn perform_other_task(device: &DeviceWrapper) {} /// /// # fn try_main() -> Result<(), Error> { -/// let mut device = nitrokey::connect()?; -/// match device.authenticate_user("123456") { -/// Ok(user) => perform_user_task(&user), -/// Err(err) => eprintln!("Could not authenticate as user: {}", err), +/// let device = nitrokey::connect()?; +/// let device = match device.authenticate_user("123456") { +/// Ok(user) => { +/// perform_user_task(&user); +/// user.device() +/// }, +/// Err((device, err)) => { +/// eprintln!("Could not authenticate as user: {}", err); +/// device +/// }, /// }; /// perform_other_task(&device); /// # Ok(()) @@ -129,10 +135,16 @@ pub enum DeviceWrapper { /// fn perform_other_task(device: &Pro) {} /// /// # fn try_main() -> Result<(), Error> { -/// let mut device = nitrokey::Pro::connect()?; -/// match device.authenticate_user("123456") { -/// Ok(user) => perform_user_task(&user), -/// Err(err) => eprintln!("Could not authenticate as user: {}", err), +/// let device = nitrokey::Pro::connect()?; +/// let device = match device.authenticate_user("123456") { +/// Ok(user) => { +/// perform_user_task(&user); +/// user.device() +/// }, +/// Err((device, err)) => { +/// eprintln!("Could not authenticate as user: {}", err); +/// device +/// }, /// }; /// perform_other_task(&device); /// # Ok(()) @@ -169,10 +181,16 @@ pub struct Pro { /// fn perform_other_task(device: &Storage) {} /// /// # fn try_main() -> Result<(), Error> { -/// let mut device = nitrokey::Storage::connect()?; -/// match device.authenticate_user("123456") { -/// Ok(user) => perform_user_task(&user), -/// Err(err) => eprintln!("Could not authenticate as user: {}", err), +/// let device = nitrokey::Storage::connect()?; +/// let device = match device.authenticate_user("123456") { +/// Ok(user) => { +/// perform_user_task(&user); +/// user.device() +/// }, +/// Err((device, err)) => { +/// eprintln!("Could not authenticate as user: {}", err); +/// device +/// }, /// }; /// perform_other_task(&device); /// # Ok(()) diff --git a/src/lib.rs b/src/lib.rs index d7a8c5e..c35829c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,7 +44,7 @@ //! # use nitrokey::Error; //! //! # fn try_main() -> Result<(), Error> { -//! let mut device = nitrokey::connect()?; +//! let device = nitrokey::connect()?; //! let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits); //! match device.authenticate_admin("12345678") { //! Ok(mut admin) => { @@ -53,7 +53,7 @@ //! Err(err) => eprintln!("Could not write slot: {}", err), //! } //! }, -//! Err(err) => eprintln!("Could not authenticate as admin: {}", err), +//! Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err), //! } //! # Ok(()) //! # } diff --git a/src/otp.rs b/src/otp.rs index a8dd20b..ee142c7 100644 --- a/src/otp.rs +++ b/src/otp.rs @@ -35,7 +35,7 @@ pub trait ConfigureOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let device = nitrokey::connect()?; /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits); /// match device.authenticate_admin("12345678") { /// Ok(mut admin) => { @@ -44,7 +44,7 @@ pub trait ConfigureOtp { /// Err(err) => eprintln!("Could not write slot: {}", err), /// } /// }, - /// Err(err) => eprintln!("Could not authenticate as admin: {}", err), + /// Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err), /// } /// # Ok(()) /// # } @@ -71,7 +71,7 @@ pub trait ConfigureOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let device = nitrokey::connect()?; /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::EightDigits); /// match device.authenticate_admin("12345678") { /// Ok(mut admin) => { @@ -80,7 +80,7 @@ pub trait ConfigureOtp { /// Err(err) => eprintln!("Could not write slot: {}", err), /// } /// }, - /// Err(err) => eprintln!("Could not authenticate as admin: {}", err), + /// Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err), /// } /// # Ok(()) /// # } @@ -104,7 +104,7 @@ pub trait ConfigureOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let device = nitrokey::connect()?; /// match device.authenticate_admin("12345678") { /// Ok(mut admin) => { /// match admin.erase_hotp_slot(1) { @@ -112,7 +112,7 @@ pub trait ConfigureOtp { /// Err(err) => eprintln!("Could not erase slot: {}", err), /// } /// }, - /// Err(err) => eprintln!("Could not authenticate as admin: {}", err), + /// Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err), /// } /// # Ok(()) /// # } @@ -134,7 +134,7 @@ pub trait ConfigureOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let device = nitrokey::connect()?; /// match device.authenticate_admin("12345678") { /// Ok(mut admin) => { /// match admin.erase_totp_slot(1) { @@ -142,7 +142,7 @@ pub trait ConfigureOtp { /// Err(err) => eprintln!("Could not erase slot: {}", err), /// } /// }, - /// Err(err) => eprintln!("Could not authenticate as admin: {}", err), + /// Err((_, err)) => eprintln!("Could not authenticate as admin: {}", err), /// } /// # Ok(()) /// # } diff --git a/tests/device.rs b/tests/device.rs index ecc3cfa..6a3683b 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -97,34 +97,41 @@ fn get_firmware_version(device: Pro) { assert!(version.minor > 0); } -fn admin_retry(device: &mut T, suffix: &str, count: u8) { - assert_any_ok!(device.authenticate_admin(&(DEFAULT_ADMIN_PIN.to_owned() + suffix))); +fn admin_retry(device: T, suffix: &str, count: u8) -> T { + let result = device.authenticate_admin(&(DEFAULT_ADMIN_PIN.to_owned() + suffix)); + let device = match result { + Ok(admin) => admin.device(), + Err((device, _)) => device, + }; assert_ok!(count, device.get_admin_retry_count()); + return device; } -fn user_retry(device: &mut T, suffix: &str, count: u8) { - assert_any_ok!(device.authenticate_user(&(DEFAULT_USER_PIN.to_owned() + suffix))); +fn user_retry(device: T, suffix: &str, count: u8) -> T { + let result = device.authenticate_user(&(DEFAULT_USER_PIN.to_owned() + suffix)); + let device = match result { + Ok(admin) => admin.device(), + Err((device, _)) => device, + }; assert_ok!(count, device.get_user_retry_count()); + return device; } #[test_device] fn get_retry_count(device: DeviceWrapper) { - let mut device = device; - - admin_retry(&mut device, "", 3); - admin_retry(&mut device, "123", 2); - admin_retry(&mut device, "456", 1); - admin_retry(&mut device, "", 3); - - user_retry(&mut device, "", 3); - user_retry(&mut device, "123", 2); - user_retry(&mut device, "456", 1); - user_retry(&mut device, "", 3); + let device = admin_retry(device, "", 3); + let device = admin_retry(device, "123", 2); + let device = admin_retry(device, "456", 1); + let device = admin_retry(device, "", 3); + + let device = user_retry(device, "", 3); + let device = user_retry(device, "123", 2); + let device = user_retry(device, "456", 1); + user_retry(device, "", 3); } #[test_device] fn config(device: DeviceWrapper) { - let mut device = device; let mut admin = unwrap_ok!(device.authenticate_admin(DEFAULT_ADMIN_PIN)); let config = Config::new(None, None, None, true); @@ -145,24 +152,19 @@ fn config(device: DeviceWrapper) { #[test_device] fn change_user_pin(device: DeviceWrapper) { - let mut device = device; - assert_any_ok!(device.authenticate_user(DEFAULT_USER_PIN)); - assert_cmd_err!( - CommandError::WrongPassword, - device.authenticate_user(USER_NEW_PASSWORD) - ); + let device = device.authenticate_user(DEFAULT_USER_PIN).unwrap().device(); + let device = device.authenticate_user(USER_NEW_PASSWORD).unwrap_err().0; - assert_ok!( - (), - device.change_user_pin(DEFAULT_USER_PIN, USER_NEW_PASSWORD) - ); + let mut device = device; + assert_ok!((), device.change_user_pin(DEFAULT_USER_PIN, USER_NEW_PASSWORD)); - assert_cmd_err!( - CommandError::WrongPassword, - device.authenticate_user(DEFAULT_USER_PIN) - ); - assert_any_ok!(device.authenticate_user(USER_NEW_PASSWORD)); + let device = device.authenticate_user(DEFAULT_USER_PIN).unwrap_err().0; + let device = device + .authenticate_user(USER_NEW_PASSWORD) + .unwrap() + .device(); + let mut device = device; let result = device.change_user_pin(DEFAULT_USER_PIN, DEFAULT_USER_PIN); assert_cmd_err!(CommandError::WrongPassword, result); @@ -171,32 +173,25 @@ fn change_user_pin(device: DeviceWrapper) { device.change_user_pin(USER_NEW_PASSWORD, DEFAULT_USER_PIN) ); - assert_any_ok!(device.authenticate_user(DEFAULT_USER_PIN)); - assert_cmd_err!( - CommandError::WrongPassword, - device.authenticate_user(USER_NEW_PASSWORD) - ); + let device = device.authenticate_user(DEFAULT_USER_PIN).unwrap().device(); + assert!(device.authenticate_user(USER_NEW_PASSWORD).is_err()); } #[test_device] fn change_admin_pin(device: DeviceWrapper) { - let mut device = device; - assert_any_ok!(device.authenticate_admin(DEFAULT_ADMIN_PIN)); - assert_cmd_err!( - CommandError::WrongPassword, - device.authenticate_admin(ADMIN_NEW_PASSWORD) - ); + let device = device.authenticate_admin(DEFAULT_ADMIN_PIN).unwrap().device(); + let mut device = device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err().0; assert_ok!( (), device.change_admin_pin(DEFAULT_ADMIN_PIN, ADMIN_NEW_PASSWORD) ); - assert_cmd_err!( - CommandError::WrongPassword, - device.authenticate_admin(DEFAULT_ADMIN_PIN) - ); - assert_any_ok!(device.authenticate_admin(ADMIN_NEW_PASSWORD)); + let device = device.authenticate_admin(DEFAULT_ADMIN_PIN).unwrap_err().0; + let mut device = device + .authenticate_admin(ADMIN_NEW_PASSWORD) + .unwrap() + .device(); assert_cmd_err!( CommandError::WrongPassword, @@ -208,28 +203,29 @@ fn change_admin_pin(device: DeviceWrapper) { device.change_admin_pin(ADMIN_NEW_PASSWORD, DEFAULT_ADMIN_PIN) ); - assert_any_ok!(device.authenticate_admin(DEFAULT_ADMIN_PIN)); - assert_cmd_err!( - CommandError::WrongPassword, - device.authenticate_admin(ADMIN_NEW_PASSWORD) - ); + let device = device.authenticate_admin(DEFAULT_ADMIN_PIN).unwrap().device(); + device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err(); } -fn require_failed_user_login(device: &mut D, password: &str) { - assert_cmd_err!( - CommandError::WrongPassword, - device.authenticate_user(password) - ); +fn require_failed_user_login(device: D, password: &str, error: CommandError) -> D +where + D: Device + Authenticate, + nitrokey::User: std::fmt::Debug, +{ + let result = device.authenticate_user(password); + assert!(result.is_err()); + let err = result.unwrap_err(); + match err.1 { + Error::CommandError(err) => assert_eq!(error, err), + _ => assert!(false), + }; + err.0 } #[test_device] fn unlock_user_pin(device: DeviceWrapper) { - let mut device = device; - assert_any_ok!(device.authenticate_user(DEFAULT_USER_PIN)); - assert_ok!( - (), - device.unlock_user_pin(DEFAULT_ADMIN_PIN, DEFAULT_USER_PIN) - ); + let mut device = device.authenticate_user(DEFAULT_USER_PIN).unwrap().device(); + assert_ok!((), device.unlock_user_pin(DEFAULT_ADMIN_PIN, DEFAULT_USER_PIN)); assert_cmd_err!( CommandError::WrongPassword, device.unlock_user_pin(DEFAULT_USER_PIN, DEFAULT_USER_PIN) @@ -237,27 +233,24 @@ fn unlock_user_pin(device: DeviceWrapper) { // block user PIN let wrong_password = DEFAULT_USER_PIN.to_owned() + "foo"; - require_failed_user_login(&mut device, &wrong_password); - require_failed_user_login(&mut device, &wrong_password); - require_failed_user_login(&mut device, &wrong_password); - require_failed_user_login(&mut device, DEFAULT_USER_PIN); + let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); + let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); + let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); + let mut device = require_failed_user_login(device, DEFAULT_USER_PIN, CommandError::WrongPassword); // unblock with current PIN assert_cmd_err!( CommandError::WrongPassword, device.unlock_user_pin(DEFAULT_USER_PIN, DEFAULT_USER_PIN) ); - assert_ok!( - (), - device.unlock_user_pin(DEFAULT_ADMIN_PIN, DEFAULT_USER_PIN) - ); - assert_any_ok!(device.authenticate_user(DEFAULT_USER_PIN)); + assert_ok!((), device.unlock_user_pin(DEFAULT_ADMIN_PIN, DEFAULT_USER_PIN)); + let device = device.authenticate_user(DEFAULT_USER_PIN).unwrap().device(); // block user PIN - require_failed_user_login(&mut device, &wrong_password); - require_failed_user_login(&mut device, &wrong_password); - require_failed_user_login(&mut device, &wrong_password); - require_failed_user_login(&mut device, DEFAULT_USER_PIN); + let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); + let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); + let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); + let mut device = require_failed_user_login(device, DEFAULT_USER_PIN, CommandError::WrongPassword); // unblock with new PIN assert_cmd_err!( @@ -286,11 +279,11 @@ fn assert_utf8_err_or_ne(left: &str, right: Result) { #[test_device] fn factory_reset(device: DeviceWrapper) { - let mut device = device; let mut admin = unwrap_ok!(device.authenticate_admin(DEFAULT_ADMIN_PIN)); let otp_data = OtpSlotData::new(1, "test", "0123468790", OtpMode::SixDigits); assert_ok!((), admin.write_totp_slot(otp_data, 30)); + let mut device = admin.device(); let mut pws = unwrap_ok!(device.get_password_safe(DEFAULT_USER_PIN)); assert_ok!((), pws.write_slot(0, "test", "testlogin", "testpw")); drop(pws); @@ -314,11 +307,12 @@ fn factory_reset(device: DeviceWrapper) { ); assert_ok!((), device.factory_reset(ADMIN_NEW_PASSWORD)); - assert_any_ok!(device.authenticate_admin(DEFAULT_ADMIN_PIN)); + let device = device.authenticate_admin(DEFAULT_ADMIN_PIN).unwrap().device(); let user = unwrap_ok!(device.authenticate_user(DEFAULT_USER_PIN)); assert_cmd_err!(CommandError::SlotNotProgrammed, user.get_totp_slot_name(1)); + let mut device = user.device(); let pws = unwrap_ok!(device.get_password_safe(DEFAULT_USER_PIN)); assert_utf8_err_or_ne("test", pws.get_slot_name(0)); assert_utf8_err_or_ne("testlogin", pws.get_slot_login(0)); @@ -341,7 +335,7 @@ fn build_aes_key(device: DeviceWrapper) { ); assert_ok!((), device.build_aes_key(DEFAULT_ADMIN_PIN)); - assert_any_ok!(device.authenticate_admin(DEFAULT_ADMIN_PIN)); + let mut device = device.authenticate_admin(DEFAULT_ADMIN_PIN).unwrap().device(); let pws = unwrap_ok!(device.get_password_safe(DEFAULT_USER_PIN)); assert_utf8_err_or_ne("test", pws.get_slot_name(0)); diff --git a/tests/otp.rs b/tests/otp.rs index d55d54a..c0bbecf 100644 --- a/tests/otp.rs +++ b/tests/otp.rs @@ -3,6 +3,7 @@ mod util; +use std::fmt::Debug; use std::ops::DerefMut; use nitrokey::{ @@ -35,9 +36,10 @@ enum TotpTimestampSize { U64, } -fn make_admin_test_device<'a, T>(device: &'a mut T) -> Admin<'a, T> +fn make_admin_test_device(device: T) -> Admin where T: Device, + (T, nitrokey::Error): Debug, { unwrap_ok!(device.authenticate_admin(DEFAULT_ADMIN_PIN)) } @@ -66,8 +68,7 @@ fn set_time(device: DeviceWrapper) { #[test_device] fn hotp_no_pin(device: DeviceWrapper) { - let mut device = device; - let mut admin = make_admin_test_device(&mut device); + let mut admin = make_admin_test_device(device); let config = Config::new(None, None, None, false); assert_ok!((), admin.write_config(config)); @@ -78,38 +79,36 @@ fn hotp_no_pin(device: DeviceWrapper) { check_hotp_codes(admin.deref_mut(), 5); configure_hotp(&mut admin, 0); - check_hotp_codes(&mut device, 0); + check_hotp_codes(&mut admin.device(), 0); } #[test_device] fn hotp_pin(device: DeviceWrapper) { - let mut device = device; - let mut admin = make_admin_test_device(&mut device); + let mut admin = make_admin_test_device(device); let config = Config::new(None, None, None, true); assert_ok!((), admin.write_config(config)); configure_hotp(&mut admin, 0); - let mut user = unwrap_ok!(device.authenticate_user(DEFAULT_USER_PIN)); + let mut user = unwrap_ok!(admin.device().authenticate_user(DEFAULT_USER_PIN)); check_hotp_codes(&mut user, 0); - assert_cmd_err!(CommandError::NotAuthorized, user.get_hotp_code(1)); + assert_cmd_err!(CommandError::NotAuthorized, user.device().get_hotp_code(1)); } #[test_device] fn hotp_slot_name(device: DeviceWrapper) { - let mut device = device; - let mut admin = make_admin_test_device(&mut device); + let mut admin = make_admin_test_device(device); let slot_data = OtpSlotData::new(1, "test-hotp", HOTP_SECRET, OtpMode::SixDigits); assert_ok!((), admin.write_hotp_slot(slot_data, 0)); + let device = admin.device(); assert_ok!("test-hotp".to_string(), device.get_hotp_slot_name(1)); assert_lib_err!(LibraryError::InvalidSlot, device.get_hotp_slot_name(4)); } #[test_device] fn hotp_error(device: DeviceWrapper) { - let mut device = device; - let mut admin = make_admin_test_device(&mut device); + let mut admin = make_admin_test_device(device); let slot_data = OtpSlotData::new(1, "", HOTP_SECRET, OtpMode::SixDigits); assert_cmd_err!(CommandError::NoName, admin.write_hotp_slot(slot_data, 0)); let slot_data = OtpSlotData::new(4, "test", HOTP_SECRET, OtpMode::SixDigits); @@ -128,8 +127,7 @@ fn hotp_error(device: DeviceWrapper) { #[test_device] fn hotp_erase(device: DeviceWrapper) { - let mut device = device; - let mut admin = make_admin_test_device(&mut device); + let mut admin = make_admin_test_device(device); let config = Config::new(None, None, None, false); assert_ok!((), admin.write_config(config)); let slot_data = OtpSlotData::new(1, "test1", HOTP_SECRET, OtpMode::SixDigits); @@ -139,6 +137,7 @@ fn hotp_erase(device: DeviceWrapper) { assert_ok!((), admin.erase_hotp_slot(1)); + let mut device = admin.device(); let result = device.get_hotp_slot_name(1); assert_cmd_err!(CommandError::SlotNotProgrammed, result); let result = device.get_hotp_code(1); @@ -175,8 +174,7 @@ fn check_totp_codes(device: &mut GenerateOtp, factor: u64, timestamp_size: TotpT #[test_device] fn totp_no_pin(device: DeviceWrapper) { - let mut device = device; - let mut admin = make_admin_test_device(&mut device); + let mut admin = make_admin_test_device(device); let config = Config::new(None, None, None, false); assert_ok!((), admin.write_config(config)); @@ -187,15 +185,14 @@ fn totp_no_pin(device: DeviceWrapper) { check_totp_codes(admin.deref_mut(), 2, TotpTimestampSize::U32); configure_totp(&mut admin, 1); - check_totp_codes(&mut device, 1, TotpTimestampSize::U32); + check_totp_codes(&mut admin.device(), 1, TotpTimestampSize::U32); } #[test_device] // Nitrokey Storage does only support timestamps that fit in a 32-bit // unsigned integer, so don't test with it. fn totp_no_pin_64(device: Pro) { - let mut device = device; - let mut admin = make_admin_test_device(&mut device); + let mut admin = make_admin_test_device(device); let config = Config::new(None, None, None, false); assert_ok!((), admin.write_config(config)); @@ -206,45 +203,43 @@ fn totp_no_pin_64(device: Pro) { check_totp_codes(admin.deref_mut(), 2, TotpTimestampSize::U64); configure_totp(&mut admin, 1); - check_totp_codes(&mut device, 1, TotpTimestampSize::U64); + check_totp_codes(&mut admin.device(), 1, TotpTimestampSize::U64); } #[test_device] fn totp_pin(device: DeviceWrapper) { - let mut device = device; - let mut admin = make_admin_test_device(&mut device); + let mut admin = make_admin_test_device(device); let config = Config::new(None, None, None, true); assert_ok!((), admin.write_config(config)); configure_totp(&mut admin, 1); - let mut user = unwrap_ok!(device.authenticate_user(DEFAULT_USER_PIN)); + let mut user = unwrap_ok!(admin.device().authenticate_user(DEFAULT_USER_PIN)); check_totp_codes(&mut user, 1, TotpTimestampSize::U32); - assert_cmd_err!(CommandError::NotAuthorized, user.get_totp_code(1)); + assert_cmd_err!(CommandError::NotAuthorized, user.device().get_totp_code(1)); } #[test_device] // See comment for totp_no_pin_64. fn totp_pin_64(device: Pro) { - let mut device = device; - let mut admin = make_admin_test_device(&mut device); + let mut admin = make_admin_test_device(device); let config = Config::new(None, None, None, true); assert_ok!((), admin.write_config(config)); configure_totp(&mut admin, 1); - let mut user = unwrap_ok!(admin.authenticate_user(DEFAULT_USER_PIN)); + let mut user = unwrap_ok!(admin.device().authenticate_user(DEFAULT_USER_PIN)); check_totp_codes(&mut user, 1, TotpTimestampSize::U64); - assert_cmd_err!(CommandError::NotAuthorized, device.get_totp_code(1)); + assert_cmd_err!(CommandError::NotAuthorized, user.device().get_totp_code(1)); } #[test_device] fn totp_slot_name(device: DeviceWrapper) { - let mut device = device; - let mut admin = make_admin_test_device(&mut device); + let mut admin = make_admin_test_device(device); let slot_data = OtpSlotData::new(1, "test-totp", TOTP_SECRET, OtpMode::EightDigits); assert_ok!((), admin.write_totp_slot(slot_data, 0)); + let device = admin.device(); let result = device.get_totp_slot_name(1); assert_ok!("test-totp", result); let result = device.get_totp_slot_name(16); @@ -253,8 +248,7 @@ fn totp_slot_name(device: DeviceWrapper) { #[test_device] fn totp_error(device: DeviceWrapper) { - let mut device = device; - let mut admin = make_admin_test_device(&mut device); + let mut admin = make_admin_test_device(device); let slot_data = OtpSlotData::new(1, "", TOTP_SECRET, OtpMode::SixDigits); assert_cmd_err!(CommandError::NoName, admin.write_totp_slot(slot_data, 0)); let slot_data = OtpSlotData::new(20, "test", TOTP_SECRET, OtpMode::SixDigits); @@ -273,8 +267,7 @@ fn totp_error(device: DeviceWrapper) { #[test_device] fn totp_erase(device: DeviceWrapper) { - let mut device = device; - let mut admin = make_admin_test_device(&mut device); + let mut admin = make_admin_test_device(device); let config = Config::new(None, None, None, false); assert_ok!((), admin.write_config(config)); let slot_data = OtpSlotData::new(1, "test1", TOTP_SECRET, OtpMode::SixDigits); @@ -284,6 +277,7 @@ fn totp_erase(device: DeviceWrapper) { assert_ok!((), admin.erase_totp_slot(1)); + let device = admin.device(); let result = device.get_totp_slot_name(1); assert_cmd_err!(CommandError::SlotNotProgrammed, result); let result = device.get_totp_code(1); diff --git a/tests/util/mod.rs b/tests/util/mod.rs index 5bd19d1..f2b20ec 100644 --- a/tests/util/mod.rs +++ b/tests/util/mod.rs @@ -3,7 +3,7 @@ #[macro_export] macro_rules! unwrap_ok { - ($val:expr) => { + ($val:expr) => {{ match $val { Ok(val) => val, Err(err) => panic!( @@ -13,12 +13,12 @@ macro_rules! unwrap_ok { err ), } - }; + }}; } #[macro_export] macro_rules! assert_any_ok { - ($val:expr) => { + ($val:expr) => {{ match &$val { Ok(_) => {} Err(err) => panic!( @@ -28,12 +28,12 @@ macro_rules! assert_any_ok { err ), } - }; + }}; } #[macro_export] macro_rules! assert_ok { - ($left:expr, $right:expr) => { + ($left:expr, $right:expr) => {{ match &$right { Ok(right) => match &$left { left => { @@ -54,7 +54,7 @@ macro_rules! assert_ok { $left, right_err ), } - }; + }}; } #[macro_export] -- cgit v1.2.3 From d95355e3d76c0c0022629e635f36a2dc325c0af2 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 5 Feb 2019 12:48:01 +0000 Subject: Revert "Store mutable reference to Device in PasswordSafe" This reverts commit 13006c00dcbd570cf8347d89557834e320427377. --- TODO.md | 1 + src/pws.rs | 24 ++++++++++++------------ tests/device.rs | 41 ++++++++++++++++++++++++++++++++--------- 3 files changed, 45 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/TODO.md b/TODO.md index d6a3509..1ff723d 100644 --- a/TODO.md +++ b/TODO.md @@ -13,6 +13,7 @@ SPDX-License-Identifier: MIT - Clear passwords from memory. - Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware issue 65][]). +- Disable creation of multiple password safes at the same time. - Check timing in Storage tests. - Consider restructuring `device::StorageStatus`. diff --git a/src/pws.rs b/src/pws.rs index a5b9d33..371de6e 100644 --- a/src/pws.rs +++ b/src/pws.rs @@ -18,7 +18,8 @@ pub const SLOT_COUNT: u8 = 16; /// The password safe stores a tuple consisting of a name, a login and a password on a slot. The /// number of available slots is [`SLOT_COUNT`][]. The slots are addressed starting with zero. To /// retrieve a password safe from a Nitrokey device, use the [`get_password_safe`][] method from -/// the [`GetPasswordSafe`][] trait. +/// the [`GetPasswordSafe`][] trait. Note that the device must live at least as long as the +/// password safe. /// /// Once the password safe has been unlocked, it can be accessed without a password. Therefore it /// is mandatory to call [`lock`][] on the corresponding device after the password store is used. @@ -57,17 +58,21 @@ pub const SLOT_COUNT: u8 = 16; /// [`GetPasswordSafe`]: trait.GetPasswordSafe.html #[derive(Debug)] pub struct PasswordSafe<'a> { - device: &'a mut dyn Device, + _device: &'a dyn Device, } /// Provides access to a [`PasswordSafe`][]. /// +/// The device that implements this trait must always live at least as long as a password safe +/// retrieved from it. +/// /// [`PasswordSafe`]: struct.PasswordSafe.html pub trait GetPasswordSafe { /// Enables and returns the password safe. /// - /// It is mandatory to lock the underlying device using [`lock`][] after the password safe has - /// been used. Otherwise, other applications can access the password store without + /// The underlying device must always live at least as long as a password safe retrieved from + /// it. It is mandatory to lock the underlying device using [`lock`][] after the password safe + /// has been used. Otherwise, other applications can access the password store without /// authentication. /// /// If this method returns an `AesDecryptionFailed` (Nitrokey Pro) or `Unknown` (Nitrokey @@ -116,17 +121,12 @@ pub trait GetPasswordSafe { } fn get_password_safe<'a>( - device: &'a mut dyn Device, + device: &'a dyn Device, user_pin: &str, ) -> Result, Error> { let user_pin_string = get_cstring(user_pin)?; - let result = get_command_result(unsafe { - nitrokey_sys::NK_enable_password_safe(user_pin_string.as_ptr()) - }); - match result { - Ok(()) => Ok(PasswordSafe { device }), - Err(err) => Err(err), - } + get_command_result(unsafe { nitrokey_sys::NK_enable_password_safe(user_pin_string.as_ptr()) }) + .map(|_| PasswordSafe { _device: device }) } fn get_pws_result(s: String) -> Result { diff --git a/tests/device.rs b/tests/device.rs index 6a3683b..5c52024 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -156,7 +156,10 @@ fn change_user_pin(device: DeviceWrapper) { let device = device.authenticate_user(USER_NEW_PASSWORD).unwrap_err().0; let mut device = device; - assert_ok!((), device.change_user_pin(DEFAULT_USER_PIN, USER_NEW_PASSWORD)); + assert_ok!( + (), + device.change_user_pin(DEFAULT_USER_PIN, USER_NEW_PASSWORD) + ); let device = device.authenticate_user(DEFAULT_USER_PIN).unwrap_err().0; let device = device @@ -179,7 +182,10 @@ fn change_user_pin(device: DeviceWrapper) { #[test_device] fn change_admin_pin(device: DeviceWrapper) { - let device = device.authenticate_admin(DEFAULT_ADMIN_PIN).unwrap().device(); + let device = device + .authenticate_admin(DEFAULT_ADMIN_PIN) + .unwrap() + .device(); let mut device = device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err().0; assert_ok!( @@ -203,7 +209,10 @@ fn change_admin_pin(device: DeviceWrapper) { device.change_admin_pin(ADMIN_NEW_PASSWORD, DEFAULT_ADMIN_PIN) ); - let device = device.authenticate_admin(DEFAULT_ADMIN_PIN).unwrap().device(); + let device = device + .authenticate_admin(DEFAULT_ADMIN_PIN) + .unwrap() + .device(); device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err(); } @@ -225,7 +234,10 @@ where #[test_device] fn unlock_user_pin(device: DeviceWrapper) { let mut device = device.authenticate_user(DEFAULT_USER_PIN).unwrap().device(); - assert_ok!((), device.unlock_user_pin(DEFAULT_ADMIN_PIN, DEFAULT_USER_PIN)); + assert_ok!( + (), + device.unlock_user_pin(DEFAULT_ADMIN_PIN, DEFAULT_USER_PIN) + ); assert_cmd_err!( CommandError::WrongPassword, device.unlock_user_pin(DEFAULT_USER_PIN, DEFAULT_USER_PIN) @@ -236,21 +248,26 @@ fn unlock_user_pin(device: DeviceWrapper) { let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); - let mut device = require_failed_user_login(device, DEFAULT_USER_PIN, CommandError::WrongPassword); + let mut device = + require_failed_user_login(device, DEFAULT_USER_PIN, CommandError::WrongPassword); // unblock with current PIN assert_cmd_err!( CommandError::WrongPassword, device.unlock_user_pin(DEFAULT_USER_PIN, DEFAULT_USER_PIN) ); - assert_ok!((), device.unlock_user_pin(DEFAULT_ADMIN_PIN, DEFAULT_USER_PIN)); + assert_ok!( + (), + device.unlock_user_pin(DEFAULT_ADMIN_PIN, DEFAULT_USER_PIN) + ); let device = device.authenticate_user(DEFAULT_USER_PIN).unwrap().device(); // block user PIN let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); - let mut device = require_failed_user_login(device, DEFAULT_USER_PIN, CommandError::WrongPassword); + let mut device = + require_failed_user_login(device, DEFAULT_USER_PIN, CommandError::WrongPassword); // unblock with new PIN assert_cmd_err!( @@ -307,7 +324,10 @@ fn factory_reset(device: DeviceWrapper) { ); assert_ok!((), device.factory_reset(ADMIN_NEW_PASSWORD)); - let device = device.authenticate_admin(DEFAULT_ADMIN_PIN).unwrap().device(); + let device = device + .authenticate_admin(DEFAULT_ADMIN_PIN) + .unwrap() + .device(); let user = unwrap_ok!(device.authenticate_user(DEFAULT_USER_PIN)); assert_cmd_err!(CommandError::SlotNotProgrammed, user.get_totp_slot_name(1)); @@ -335,7 +355,10 @@ fn build_aes_key(device: DeviceWrapper) { ); assert_ok!((), device.build_aes_key(DEFAULT_ADMIN_PIN)); - let mut device = device.authenticate_admin(DEFAULT_ADMIN_PIN).unwrap().device(); + let mut device = device + .authenticate_admin(DEFAULT_ADMIN_PIN) + .unwrap() + .device(); let pws = unwrap_ok!(device.get_password_safe(DEFAULT_USER_PIN)); assert_utf8_err_or_ne("test", pws.get_slot_name(0)); -- cgit v1.2.3 From e923c8b1ddaeafc9494ae86738bed9ad0e0e6e8f Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Fri, 5 Jul 2019 22:59:27 +0000 Subject: Update nitrokey-sys to version 3.5 As the return type of the NK_get_{major,minor}_firmware_version methods changed with libnitrokey 3.5, we also have to adapt our get_firmware_version function in device.rs. This patch also updates the changelog and the todo list with the changes caused by the new libnitrokey version. --- CHANGELOG.md | 1 + Cargo.toml | 2 +- TODO.md | 12 ++++++++++++ src/device.rs | 8 ++------ 4 files changed, 16 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index e98e857..e471f9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ SPDX-License-Identifier: MIT - Implement `DerefMut` for `User` and `Admin`. - Add `device_mut` method to `DeviceWrapper`. - Require a mutable `Device` reference if a method changes the device state. +- Update the `nitrokey-sys` dependency to version 3.5.0. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/Cargo.toml b/Cargo.toml index 355f001..83cfa9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ exclude = [".builds/*"] [dependencies] libc = "0.2" -nitrokey-sys = "~3.4" +nitrokey-sys = "3.5" rand_core = {version = "0.3", default-features = false, features = ["std"] } rand_os = {version = "0.1"} diff --git a/TODO.md b/TODO.md index 1ff723d..d7c8874 100644 --- a/TODO.md +++ b/TODO.md @@ -10,6 +10,18 @@ SPDX-License-Identifier: MIT - `NK_get_progress_bar_value` - `NK_list_devices_by_cpuID` - `NK_connect_with_ID` +- Evaluate the changes in libnitrokey 3.5: + - `NK_get_SD_usage_data` + - `NK_get_status` + - `NK_get_status_as_string` + - `NK_list_devices` + - `NK_free_device_info` + - `NK_connect_with_path` + - `NK_enable_firmware_update_pro` + - `NK_change_firmware_password_pro` + - `NK_read_HOTP_slot` + - `NK_status` (deprecated) + - `NK_get_progress_bar_value` (return value) - Clear passwords from memory. - Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware issue 65][]). diff --git a/src/device.rs b/src/device.rs index f6492cd..6597ba9 100644 --- a/src/device.rs +++ b/src/device.rs @@ -399,13 +399,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { fn get_firmware_version(&self) -> Result { 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() })?; - let max = i32::from(u8::max_value()); - if major < 0 || minor < 0 || major > max || minor > max { - return Err(Error::UnexpectedError); - } Ok(FirmwareVersion { - major: major as u8, - minor: minor as u8, + major, + minor, }) } -- cgit v1.2.3 From 34f7022ce078dc132734873a3efdefe3d6d4399a Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 7 Jul 2019 20:40:20 +0000 Subject: Fix formatting error in device.rs --- src/device.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src') diff --git a/src/device.rs b/src/device.rs index 6597ba9..51551c2 100644 --- a/src/device.rs +++ b/src/device.rs @@ -399,10 +399,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { fn get_firmware_version(&self) -> Result { 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, - }) + Ok(FirmwareVersion { major, minor }) } /// Returns the current configuration of the Nitrokey device. -- cgit v1.2.3 From a52676d9577f587e0f4d8e47ddc71ba34f0b31ca Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 27 Jan 2019 15:04:19 +0000 Subject: Add ConcurrentAccessError and PoisonError variants This patch prepares the refactoring of the connection methods by introducing the Error variants ConcurrentAccessError and PoisonError. ConcurrentAccessError indicates that the user tried to connect to obtain a token that is currently locked, and PoisonError indicates that a lock has been poisoned, i. e. a thread panicked while accessing using a token. --- CHANGELOG.md | 2 ++ src/error.rs | 28 ++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 7623a33..046b609 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,8 @@ SPDX-License-Identifier: MIT - Update the `nitrokey-sys` dependency to version 3.5.0. - Update the `nitrokey-test` dependency to version 0.2.1 and add the `nitrokey-test-state` dependency in version 0.1.0. +- Refactor connection management: + - Add `ConcurrentAccessError` and `PoisonError` `Error` variants. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/error.rs b/src/error.rs index 1730171..c6b19db 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,6 +5,7 @@ use std::error; use std::fmt; use std::os::raw; use std::str; +use std::sync; use crate::device; @@ -13,11 +14,15 @@ use crate::device; pub enum Error { /// An error reported by the Nitrokey device in the response packet. CommandError(CommandError), - /// A device communication. + /// A device communication error. CommunicationError(CommunicationError), + /// An error occurred due to concurrent access to the Nitrokey device. + ConcurrentAccessError, /// A library usage error. LibraryError(LibraryError), - /// An error that occured during random number generation. + /// An error that occurred due to a poisoned lock. + PoisonError, + /// An error that occurred during random number generation. RandError(Box), /// An error that is caused by an unexpected value returned by libnitrokey. UnexpectedError, @@ -65,6 +70,21 @@ impl From for Error { } } +impl From> for Error { + fn from(_error: sync::PoisonError) -> Self { + Error::PoisonError + } +} + +impl From> for Error { + fn from(error: sync::TryLockError) -> Self { + match error { + sync::TryLockError::Poisoned(err) => err.into(), + sync::TryLockError::WouldBlock => Error::ConcurrentAccessError, + } + } +} + impl From<(T, Error)> for Error { fn from((_, err): (T, Error)) -> Self { err @@ -76,7 +96,9 @@ impl error::Error for Error { 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 => None, Error::RandError(ref err) => Some(err.as_ref()), Error::UnexpectedError => None, Error::UnknownError(_) => None, @@ -90,7 +112,9 @@ impl fmt::Display for Error { match *self { Error::CommandError(ref err) => write!(f, "Command error: {}", err), 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::UnexpectedError => write!(f, "An unexpected error occurred"), Error::UnknownError(ref err) => write!(f, "Unknown error: {}", err), -- cgit v1.2.3 From 588066f415e956fdcd2c6f6216c52b25911a3b1d Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 27 Jan 2019 15:43:32 +0000 Subject: Add Manager struct to manage Nitrokey connections As part of the connection refactoring, we introduce the Manager struct that deals with connection management. To make sure there can be only once instance of the manager, we add a global static Mutex that holds the single Manager instance. We use the struct to ensure that the user can only connect to one device at a time. This also changes the Error::PoisonError variant to store the sync::PoisonError. This allows the user to call into_inner on the PoisonError to retrieve the MutexGuard and to ignore the error (for example useful during testing). --- CHANGELOG.md | 1 + Cargo.toml | 1 + src/error.rs | 16 +++++++-------- src/lib.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/lib.rs | 16 +++++++++++++++ 5 files changed, 92 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 046b609..e67fd81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ SPDX-License-Identifier: MIT `nitrokey-test-state` dependency in version 0.1.0. - Refactor connection management: - Add `ConcurrentAccessError` and `PoisonError` `Error` variants. + - Add the `Manager` struct that manages connections to Nitrokey devices. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/Cargo.toml b/Cargo.toml index d26bd88..7daadd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ license = "MIT" exclude = [".builds/*"] [dependencies] +lazy_static = "1.2.0" libc = "0.2" nitrokey-sys = "3.5" rand_core = {version = "0.3", default-features = false, features = ["std"] } diff --git a/src/error.rs b/src/error.rs index c6b19db..b84f5eb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,7 +21,7 @@ pub enum Error { /// A library usage error. LibraryError(LibraryError), /// An error that occurred due to a poisoned lock. - PoisonError, + PoisonError(sync::PoisonError>), /// An error that occurred during random number generation. RandError(Box), /// An error that is caused by an unexpected value returned by libnitrokey. @@ -70,14 +70,14 @@ impl From for Error { } } -impl From> for Error { - fn from(_error: sync::PoisonError) -> Self { - Error::PoisonError +impl From>> for Error { + fn from(error: sync::PoisonError>) -> Self { + Error::PoisonError(error) } } -impl From> for Error { - fn from(error: sync::TryLockError) -> Self { +impl From>> for Error { + fn from(error: sync::TryLockError>) -> Self { match error { sync::TryLockError::Poisoned(err) => err.into(), sync::TryLockError::WouldBlock => Error::ConcurrentAccessError, @@ -98,7 +98,7 @@ impl error::Error for Error { Error::CommunicationError(ref err) => Some(err), Error::ConcurrentAccessError => None, Error::LibraryError(ref err) => Some(err), - Error::PoisonError => None, + Error::PoisonError(ref err) => Some(err), Error::RandError(ref err) => Some(err.as_ref()), Error::UnexpectedError => None, Error::UnknownError(_) => None, @@ -114,7 +114,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::PoisonError(_) => write!(f, "Internal error: poisoned lock"), Error::RandError(ref err) => write!(f, "RNG error: {}", err), Error::UnexpectedError => write!(f, "An unexpected error occurred"), Error::UnknownError(ref err) => write!(f, "Unknown error: {}", err), diff --git a/src/lib.rs b/src/lib.rs index c35829c..573f45f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,6 +89,9 @@ #![warn(missing_docs, rust_2018_compatibility, rust_2018_idioms, unused)] +#[macro_use(lazy_static)] +extern crate lazy_static; + mod auth; mod config; mod device; @@ -98,6 +101,8 @@ mod pws; mod util; use std::fmt; +use std::marker; +use std::sync; use nitrokey_sys; @@ -117,6 +122,10 @@ pub const DEFAULT_ADMIN_PIN: &str = "12345678"; /// The default user PIN for all Nitrokey devices. pub const DEFAULT_USER_PIN: &str = "123456"; +lazy_static! { + static ref MANAGER: sync::Mutex = sync::Mutex::new(Manager::new()); +} + /// A version of the libnitrokey library. /// /// Use the [`get_library_version`](fn.get_library_version.html) function to query the library @@ -147,6 +156,63 @@ impl fmt::Display for Version { } } +/// A manager for connections to Nitrokey devices. +/// +/// Currently, libnitrokey only provides access to one Nitrokey device at the same time. This +/// manager struct makes sure that `nitrokey-rs` does not try to connect to two devices at the same +/// time. +/// +/// To obtain an instance of this manager, use the [`take`][] function. +/// +/// [`take`]: fn.take.html +#[derive(Debug)] +pub struct Manager { + marker: marker::PhantomData<()>, +} + +impl Manager { + fn new() -> Self { + Manager { + marker: marker::PhantomData, + } + } +} + +/// Take an instance of the connection manager, blocking until an instance is available. +/// +/// There may only be one [`Manager`][] instance at the same time. If there already is an +/// instance, this method blocks. If you want a non-blocking version, use [`take`][]. +/// +/// # Errors +/// +/// - [`PoisonError`][] if the lock is poisoned +/// +/// [`take`]: fn.take.html +/// [`PoisonError`]: struct.Error.html#variant.PoisonError +/// [`Manager`]: struct.Manager.html +pub fn take_blocking() -> Result, Error> { + MANAGER.lock().map_err(Into::into) +} + +/// Try to take an instance of the connection manager. +/// +/// There may only be one [`Manager`][] instance at the same time. If there already is an +/// instance, a [`ConcurrentAccessError`][] is returned. If you want a blocking version, use +/// [`take_blocking`][]. +/// +/// # Errors +/// +/// - [`ConcurrentAccessError`][] if the token for the `Manager` instance cannot be locked +/// - [`PoisonError`][] if the lock is poisoned +/// +/// [`take_blocking`]: fn.take_blocking.html +/// [`ConcurrentAccessError`]: struct.Error.html#variant.ConcurrentAccessError +/// [`PoisonError`]: struct.Error.html#variant.PoisonError +/// [`Manager`]: struct.Manager.html +pub fn take() -> Result, Error> { + MANAGER.try_lock().map_err(Into::into) +} + /// Enables or disables debug output. Calling this method with `true` is equivalent to setting the /// log level to `Debug`; calling it with `false` is equivalent to the log level `Error` (see /// [`set_log_level`][]). diff --git a/tests/lib.rs b/tests/lib.rs index 8ab75f6..25aae0f 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -10,3 +10,19 @@ fn get_library_version() { assert!(version.git.is_empty() || version.git.starts_with("v")); assert!(version.major > 0); } + +#[test] +fn take_manager() { + assert!(nitrokey::take().is_ok()); + + let result = nitrokey::take(); + assert!(result.is_ok()); + let result2 = nitrokey::take(); + match result2 { + Ok(_) => panic!("Expected error, got Ok(_)!"), + Err(nitrokey::Error::ConcurrentAccessError) => {} + Err(err) => panic!("Expected ConcurrentAccessError, got {}", err), + } + drop(result); + assert!(nitrokey::take().is_ok()); +} -- cgit v1.2.3 From 54d23475aa3b712a539bad129fe37223173268f2 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 27 Jan 2019 17:44:59 +0000 Subject: Move the connect function into Manager As part of the connection refactoring, we replace the connect function with the Manager::connect method. To maintain compatibility with nitrokey-test, the connect function is not removed but marked as deprecated. --- CHANGELOG.md | 1 + src/device.rs | 12 +++--------- src/lib.rs | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index e67fd81..a3a9afe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ SPDX-License-Identifier: MIT - Refactor connection management: - Add `ConcurrentAccessError` and `PoisonError` `Error` variants. - Add the `Manager` struct that manages connections to Nitrokey devices. + - Deprecate the `connect` function. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/device.rs b/src/device.rs index 51551c2..653c5d1 100644 --- a/src/device.rs +++ b/src/device.rs @@ -647,15 +647,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { /// ``` /// /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected +#[deprecated(since = "0.4.0", note = "use `nitrokey::Manager::connect` instead")] pub fn connect() -> Result { - if unsafe { nitrokey_sys::NK_login_auto() } == 1 { - match get_connected_device() { - Some(wrapper) => Ok(wrapper), - None => Err(CommunicationError::NotConnected.into()), - } - } else { - Err(CommunicationError::NotConnected.into()) - } + crate::take()?.connect().map_err(Into::into) } /// Connects to a Nitrokey device of the given model. @@ -702,7 +696,7 @@ fn create_device_wrapper(model: Model) -> DeviceWrapper { } } -fn get_connected_device() -> Option { +pub(crate) fn get_connected_device() -> Option { get_connected_model().map(create_device_wrapper) } diff --git a/src/lib.rs b/src/lib.rs index 573f45f..9f88be3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,6 +108,7 @@ use nitrokey_sys; pub use crate::auth::{Admin, Authenticate, User}; pub use crate::config::Config; +#[allow(deprecated)] pub use crate::device::{ connect, connect_model, Device, DeviceWrapper, Model, Pro, SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus, @@ -176,6 +177,43 @@ impl Manager { marker: marker::PhantomData, } } + + /// Connects to a Nitrokey device. + /// + /// This method can be used to connect to any connected device, both a Nitrokey Pro and a + /// Nitrokey Storage. + /// + /// # Errors + /// + /// - [`NotConnected`][] if no Nitrokey device is connected + /// + /// # Example + /// + /// ``` + /// use nitrokey::DeviceWrapper; + /// + /// fn do_something(device: DeviceWrapper) {} + /// + /// # fn main() -> Result<(), nitrokey::Error> { + /// match nitrokey::take()?.connect() { + /// Ok(device) => do_something(device), + /// Err(err) => println!("Could not connect to a Nitrokey: {}", err), + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected + pub fn connect(&mut self) -> Result { + if unsafe { nitrokey_sys::NK_login_auto() } == 1 { + match device::get_connected_device() { + Some(wrapper) => Ok(wrapper), + None => Err(CommunicationError::NotConnected.into()), + } + } else { + Err(CommunicationError::NotConnected.into()) + } + } } /// Take an instance of the connection manager, blocking until an instance is available. -- cgit v1.2.3 From bd7c7a5fdf0ae66a1ff2f00beb5ed4c2e6994ca1 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 27 Jan 2019 18:07:59 +0000 Subject: Move the connect_model function into Manager As part of the connection refactoring, this patch moves the connect_model function to the Manager struct. As the connect_model function is not used by nitrokey-test, it is removed. --- CHANGELOG.md | 1 + src/device.rs | 33 ++------------------------------- src/lib.rs | 36 ++++++++++++++++++++++++++++++++++-- tests/device.rs | 12 ++++++++---- 4 files changed, 45 insertions(+), 37 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index a3a9afe..ffc52e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ SPDX-License-Identifier: MIT - Add `ConcurrentAccessError` and `PoisonError` `Error` variants. - Add the `Manager` struct that manages connections to Nitrokey devices. - Deprecate the `connect` function. + - Remove the `connect_model` function. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/device.rs b/src/device.rs index 653c5d1..f28558d 100644 --- a/src/device.rs +++ b/src/device.rs @@ -652,35 +652,6 @@ pub fn connect() -> Result { crate::take()?.connect().map_err(Into::into) } -/// Connects to a Nitrokey device of the given model. -/// -/// # Errors -/// -/// - [`NotConnected`][] if no Nitrokey device of the given model is connected -/// -/// # Example -/// -/// ``` -/// use nitrokey::DeviceWrapper; -/// use nitrokey::Model; -/// -/// fn do_something(device: DeviceWrapper) {} -/// -/// match nitrokey::connect_model(Model::Pro) { -/// Ok(device) => do_something(device), -/// Err(err) => eprintln!("Could not connect to a Nitrokey Pro: {}", err), -/// } -/// ``` -/// -/// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected -pub fn connect_model(model: Model) -> Result { - if connect_enum(model) { - Ok(create_device_wrapper(model)) - } else { - Err(CommunicationError::NotConnected.into()) - } -} - fn get_connected_model() -> Option { match unsafe { nitrokey_sys::NK_get_device_model() } { nitrokey_sys::NK_device_model_NK_PRO => Some(Model::Pro), @@ -689,7 +660,7 @@ fn get_connected_model() -> Option { } } -fn create_device_wrapper(model: Model) -> DeviceWrapper { +pub(crate) fn create_device_wrapper(model: Model) -> DeviceWrapper { match model { Model::Pro => Pro::new().into(), Model::Storage => Storage::new().into(), @@ -700,7 +671,7 @@ pub(crate) fn get_connected_device() -> Option { get_connected_model().map(create_device_wrapper) } -fn connect_enum(model: Model) -> bool { +pub(crate) fn connect_enum(model: Model) -> bool { let model = match model { Model::Storage => nitrokey_sys::NK_device_model_NK_STORAGE, Model::Pro => nitrokey_sys::NK_device_model_NK_PRO, diff --git a/src/lib.rs b/src/lib.rs index 9f88be3..784dc6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,8 +110,8 @@ pub use crate::auth::{Admin, Authenticate, User}; pub use crate::config::Config; #[allow(deprecated)] pub use crate::device::{ - connect, connect_model, Device, DeviceWrapper, Model, Pro, SdCardData, Storage, - StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus, + connect, Device, DeviceWrapper, Model, Pro, SdCardData, Storage, StorageProductionInfo, + StorageStatus, VolumeMode, VolumeStatus, }; pub use crate::error::{CommandError, CommunicationError, Error, LibraryError}; pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData}; @@ -214,6 +214,38 @@ impl Manager { Err(CommunicationError::NotConnected.into()) } } + + /// Connects to a Nitrokey device of the given model. + /// + /// # Errors + /// + /// - [`NotConnected`][] if no Nitrokey device of the given model is connected + /// + /// # Example + /// + /// ``` + /// use nitrokey::DeviceWrapper; + /// use nitrokey::Model; + /// + /// fn do_something(device: DeviceWrapper) {} + /// + /// # fn main() -> Result<(), nitrokey::Error> { + /// match nitrokey::take()?.connect_model(Model::Pro) { + /// Ok(device) => do_something(device), + /// Err(err) => println!("Could not connect to a Nitrokey Pro: {}", err), + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected + pub fn connect_model(&mut self, model: Model) -> Result { + if device::connect_enum(model) { + Ok(device::create_device_wrapper(model)) + } else { + Err(CommunicationError::NotConnected.into()) + } + } } /// Take an instance of the connection manager, blocking until an instance is available. diff --git a/tests/device.rs b/tests/device.rs index 5c52024..527d6f2 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -36,11 +36,15 @@ fn connect_no_device() { assert_cmu_err!(CommunicationError::NotConnected, nitrokey::connect()); assert_cmu_err!( CommunicationError::NotConnected, - nitrokey::connect_model(nitrokey::Model::Pro) + nitrokey::take() + .unwrap() + .connect_model(nitrokey::Model::Pro) ); assert_cmu_err!( CommunicationError::NotConnected, - nitrokey::connect_model(nitrokey::Model::Storage) + nitrokey::take() + .unwrap() + .connect_model(nitrokey::Model::Storage) ); assert_cmu_err!(CommunicationError::NotConnected, nitrokey::Pro::connect()); assert_cmu_err!( @@ -55,7 +59,7 @@ fn connect_pro(device: Pro) { drop(device); assert_any_ok!(nitrokey::connect()); - assert_any_ok!(nitrokey::connect_model(nitrokey::Model::Pro)); + assert_any_ok!(nitrokey::take().unwrap().connect_model(nitrokey::Model::Pro)); assert_any_ok!(nitrokey::Pro::connect()); } @@ -65,7 +69,7 @@ fn connect_storage(device: Storage) { drop(device); assert_any_ok!(nitrokey::connect()); - assert_any_ok!(nitrokey::connect_model(nitrokey::Model::Storage)); + assert_any_ok!(nitrokey::take().unwrap().connect_model(nitrokey::Model::Storage)); assert_any_ok!(nitrokey::Storage::connect()); } -- cgit v1.2.3 From 379bc798477a1de7ffda923c5d10ca63aebae25f Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 27 Jan 2019 18:21:08 +0000 Subject: Move {Pro, Storage}::connect into Manager As part of the connection refactoring, this patch moves the connect methods of the Pro and Storage structs into the Manager struct. To maintain compatibility with nitrokey-test, the old methods are not removed but marked as deprecated. --- CHANGELOG.md | 2 +- src/device.rs | 25 ++++++++++------------- src/lib.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/device.rs | 4 ++-- 4 files changed, 75 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index ffc52e6..41b529a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,7 +44,7 @@ SPDX-License-Identifier: MIT - Refactor connection management: - Add `ConcurrentAccessError` and `PoisonError` `Error` variants. - Add the `Manager` struct that manages connections to Nitrokey devices. - - Deprecate the `connect` function. + - Deprecate `connect`, `Pro::connect` and `Storage::connect`. - Remove the `connect_model` function. # v0.3.4 (2019-01-20) diff --git a/src/device.rs b/src/device.rs index f28558d..758691d 100644 --- a/src/device.rs +++ b/src/device.rs @@ -9,7 +9,7 @@ use nitrokey_sys; use crate::auth::Authenticate; use crate::config::{Config, RawConfig}; -use crate::error::{CommunicationError, Error}; +use crate::error::Error; use crate::otp::GenerateOtp; use crate::pws::GetPasswordSafe; use crate::util::{ @@ -755,16 +755,12 @@ impl Pro { /// ``` /// /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected + #[deprecated(since = "0.4.0", note = "use `nitrokey::Manager::connect_pro` instead")] pub fn connect() -> Result { - // TODO: maybe Option instead of Result? - if connect_enum(Model::Pro) { - Ok(Pro::new()) - } else { - Err(CommunicationError::NotConnected.into()) - } + crate::take()?.connect_pro().map_err(Into::into) } - fn new() -> Pro { + pub(crate) fn new() -> Pro { Pro { marker: marker::PhantomData, } @@ -808,16 +804,15 @@ impl Storage { /// ``` /// /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected + #[deprecated( + since = "0.4.0", + note = "use `nitrokey::Manager::connect_storage` instead" + )] pub fn connect() -> Result { - // TODO: maybe Option instead of Result? - if connect_enum(Model::Storage) { - Ok(Storage::new()) - } else { - Err(CommunicationError::NotConnected.into()) - } + crate::take()?.connect_storage().map_err(Into::into) } - fn new() -> Storage { + pub(crate) fn new() -> Storage { Storage { marker: marker::PhantomData, } diff --git a/src/lib.rs b/src/lib.rs index 784dc6f..dc3432a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -246,6 +246,68 @@ impl Manager { Err(CommunicationError::NotConnected.into()) } } + + /// Connects to a Nitrokey Pro. + /// + /// # Errors + /// + /// - [`NotConnected`][] if no Nitrokey device of the given model is connected + /// + /// # Example + /// + /// ``` + /// use nitrokey::Pro; + /// + /// fn use_pro(device: Pro) {} + /// + /// # fn main() -> Result<(), nitrokey::Error> { + /// match nitrokey::take()?.connect_pro() { + /// Ok(device) => use_pro(device), + /// Err(err) => println!("Could not connect to the Nitrokey Pro: {}", err), + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected + pub fn connect_pro(&mut self) -> Result { + if device::connect_enum(device::Model::Pro) { + Ok(device::Pro::new()) + } else { + Err(CommunicationError::NotConnected.into()) + } + } + + /// Connects to a Nitrokey Storage. + /// + /// # Errors + /// + /// - [`NotConnected`][] if no Nitrokey device of the given model is connected + /// + /// # Example + /// + /// ``` + /// use nitrokey::Storage; + /// + /// fn use_storage(device: Storage) {} + /// + /// # fn main() -> Result<(), nitrokey::Error> { + /// match nitrokey::take()?.connect_storage() { + /// Ok(device) => use_storage(device), + /// Err(err) => println!("Could not connect to the Nitrokey Storage: {}", err), + /// } + /// # Ok(()) + /// # } + /// ``` + /// + /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected + pub fn connect_storage(&mut self) -> Result { + if device::connect_enum(Model::Storage) { + Ok(Storage::new()) + } else { + Err(CommunicationError::NotConnected.into()) + } + } } /// Take an instance of the connection manager, blocking until an instance is available. diff --git a/tests/device.rs b/tests/device.rs index 527d6f2..b377f2e 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -60,7 +60,7 @@ fn connect_pro(device: Pro) { assert_any_ok!(nitrokey::connect()); assert_any_ok!(nitrokey::take().unwrap().connect_model(nitrokey::Model::Pro)); - assert_any_ok!(nitrokey::Pro::connect()); + assert_any_ok!(nitrokey::take().unwrap().connect_pro()); } #[test_device] @@ -70,7 +70,7 @@ fn connect_storage(device: Storage) { assert_any_ok!(nitrokey::connect()); assert_any_ok!(nitrokey::take().unwrap().connect_model(nitrokey::Model::Storage)); - assert_any_ok!(nitrokey::Storage::connect()); + assert_any_ok!(nitrokey::take().unwrap().connect_storage()); } fn assert_empty_serial_number() { -- cgit v1.2.3 From fe2f39826ade5a156945dabb8c8ab725378a15c1 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 27 Jan 2019 18:42:14 +0000 Subject: Store mutable reference to Manager in Device In the last patches, we ensured that devices can only be obtained using the Manager struct. But we did not ensure that there is only one device at a time. This patch adds a mutable reference to the Manager instance to the Device implementations. The borrow checker makes sure that there is only one mutable reference at a time. In this patch, we have to remove the old connect, Pro::connect and Storage::connect functions as they do no longer compile. (They discard the MutexGuard which invalidates the reference to the Manager.) Therefore the tests do no longer compile. --- CHANGELOG.md | 3 +- src/auth.rs | 18 +++---- src/device.rs | 160 +++++++++++++++------------------------------------------- src/lib.rs | 23 ++++----- src/pws.rs | 6 +-- 5 files changed, 63 insertions(+), 147 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 41b529a..88e68dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,8 +44,7 @@ SPDX-License-Identifier: MIT - Refactor connection management: - Add `ConcurrentAccessError` and `PoisonError` `Error` variants. - Add the `Manager` struct that manages connections to Nitrokey devices. - - Deprecate `connect`, `Pro::connect` and `Storage::connect`. - - Remove the `connect_model` function. + - Remove `connect`, `connect_model`, `Pro::connect` and `Storage::connect`. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/auth.rs b/src/auth.rs index f9f50fa..2ed7bfc 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -170,14 +170,14 @@ where } } -fn authenticate_user_wrapper( +fn authenticate_user_wrapper<'a, T, C>( device: T, constructor: C, password: &str, -) -> Result, (DeviceWrapper, Error)> +) -> Result>, (DeviceWrapper<'a>, Error)> where T: Device, - C: Fn(T) -> DeviceWrapper, + C: Fn(T) -> DeviceWrapper<'a>, { let result = device.authenticate_user(password); match result { @@ -186,14 +186,14 @@ where } } -fn authenticate_admin_wrapper( +fn authenticate_admin_wrapper<'a, T, C>( device: T, constructor: C, password: &str, -) -> Result, (DeviceWrapper, Error)> +) -> Result>, (DeviceWrapper<'a>, Error)> where T: Device, - C: Fn(T) -> DeviceWrapper, + C: Fn(T) -> DeviceWrapper<'a>, { let result = device.authenticate_admin(password); match result { @@ -377,7 +377,7 @@ impl AuthenticatedDevice for Admin { } } -impl Authenticate for DeviceWrapper { +impl<'a> Authenticate for DeviceWrapper<'a> { fn authenticate_user(self, password: &str) -> Result, (Self, Error)> { match self { DeviceWrapper::Storage(storage) => { @@ -399,7 +399,7 @@ impl Authenticate for DeviceWrapper { } } -impl Authenticate for Pro { +impl<'a> Authenticate for Pro<'a> { fn authenticate_user(self, password: &str) -> Result, (Self, Error)> { authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr) @@ -413,7 +413,7 @@ impl Authenticate for Pro { } } -impl Authenticate for Storage { +impl<'a> Authenticate for Storage<'a> { fn authenticate_user(self, password: &str) -> Result, (Self, Error)> { authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr) diff --git a/src/device.rs b/src/device.rs index 758691d..50ff071 100644 --- a/src/device.rs +++ b/src/device.rs @@ -2,14 +2,13 @@ // SPDX-License-Identifier: MIT use std::fmt; -use std::marker; use libc; use nitrokey_sys; use crate::auth::Authenticate; use crate::config::{Config, RawConfig}; -use crate::error::Error; +use crate::error::{CommunicationError, Error}; use crate::otp::GenerateOtp; use crate::pws::GetPasswordSafe; use crate::util::{ @@ -109,11 +108,11 @@ impl fmt::Display for VolumeMode { /// /// [`connect`]: fn.connect.html #[derive(Debug)] -pub enum DeviceWrapper { +pub enum DeviceWrapper<'a> { /// A Nitrokey Storage device. - Storage(Storage), + Storage(Storage<'a>), /// A Nitrokey Pro device. - Pro(Pro), + Pro(Pro<'a>), } /// A Nitrokey Pro device without user or admin authentication. @@ -156,10 +155,8 @@ pub enum DeviceWrapper { /// [`connect`]: fn.connect.html /// [`Pro::connect`]: #method.connect #[derive(Debug)] -pub struct Pro { - // make sure that users cannot directly instantiate this type - #[doc(hidden)] - marker: marker::PhantomData<()>, +pub struct Pro<'a> { + manager: &'a mut crate::Manager, } /// A Nitrokey Storage device without user or admin authentication. @@ -202,10 +199,8 @@ pub struct Pro { /// [`connect`]: fn.connect.html /// [`Storage::connect`]: #method.connect #[derive(Debug)] -pub struct Storage { - // make sure that users cannot directly instantiate this type - #[doc(hidden)] - marker: marker::PhantomData<()>, +pub struct Storage<'a> { + manager: &'a mut crate::Manager, } /// The status of a volume on a Nitrokey Storage device. @@ -626,32 +621,6 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { } } -/// Connects to a Nitrokey device. This method can be used to connect to any connected device, -/// both a Nitrokey Pro and a Nitrokey Storage. -/// -/// # Errors -/// -/// - [`NotConnected`][] if no Nitrokey device is connected -/// -/// # Example -/// -/// ``` -/// use nitrokey::DeviceWrapper; -/// -/// fn do_something(device: DeviceWrapper) {} -/// -/// match nitrokey::connect() { -/// Ok(device) => do_something(device), -/// Err(err) => eprintln!("Could not connect to a Nitrokey: {}", err), -/// } -/// ``` -/// -/// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected -#[deprecated(since = "0.4.0", note = "use `nitrokey::Manager::connect` instead")] -pub fn connect() -> Result { - crate::take()?.connect().map_err(Into::into) -} - fn get_connected_model() -> Option { match unsafe { nitrokey_sys::NK_get_device_model() } { nitrokey_sys::NK_device_model_NK_PRO => Some(Model::Pro), @@ -660,15 +629,23 @@ fn get_connected_model() -> Option { } } -pub(crate) fn create_device_wrapper(model: Model) -> DeviceWrapper { +pub(crate) fn create_device_wrapper( + manager: &mut crate::Manager, + model: Model, +) -> DeviceWrapper<'_> { match model { - Model::Pro => Pro::new().into(), - Model::Storage => Storage::new().into(), + Model::Pro => Pro::new(manager).into(), + Model::Storage => Storage::new(manager).into(), } } -pub(crate) fn get_connected_device() -> Option { - get_connected_model().map(create_device_wrapper) +pub(crate) fn get_connected_device( + manager: &mut crate::Manager, +) -> Result, Error> { + match get_connected_model() { + Some(model) => Ok(create_device_wrapper(manager, model)), + None => Err(CommunicationError::NotConnected.into()), + } } pub(crate) fn connect_enum(model: Model) -> bool { @@ -679,7 +656,7 @@ pub(crate) fn connect_enum(model: Model) -> bool { unsafe { nitrokey_sys::NK_login_enum(model) == 1 } } -impl DeviceWrapper { +impl<'a> DeviceWrapper<'a> { fn device(&self) -> &dyn Device { match *self { DeviceWrapper::Storage(ref storage) => storage, @@ -695,19 +672,19 @@ impl DeviceWrapper { } } -impl From for DeviceWrapper { - fn from(device: Pro) -> Self { +impl<'a> From> for DeviceWrapper<'a> { + fn from(device: Pro<'a>) -> Self { DeviceWrapper::Pro(device) } } -impl From for DeviceWrapper { - fn from(device: Storage) -> Self { +impl<'a> From> for DeviceWrapper<'a> { + fn from(device: Storage<'a>) -> Self { DeviceWrapper::Storage(device) } } -impl GenerateOtp for DeviceWrapper { +impl<'a> GenerateOtp for DeviceWrapper<'a> { fn get_hotp_slot_name(&self, slot: u8) -> Result { self.device().get_hotp_slot_name(slot) } @@ -725,7 +702,7 @@ impl GenerateOtp for DeviceWrapper { } } -impl Device for DeviceWrapper { +impl<'a> Device for DeviceWrapper<'a> { fn get_model(&self) -> Model { match *self { DeviceWrapper::Pro(_) => Model::Pro, @@ -734,40 +711,13 @@ impl Device for DeviceWrapper { } } -impl Pro { - /// Connects to a Nitrokey Pro. - /// - /// # Errors - /// - /// - [`NotConnected`][] if no Nitrokey device of the given model is connected - /// - /// # Example - /// - /// ``` - /// use nitrokey::Pro; - /// - /// fn use_pro(device: Pro) {} - /// - /// match nitrokey::Pro::connect() { - /// Ok(device) => use_pro(device), - /// Err(err) => eprintln!("Could not connect to the Nitrokey Pro: {}", err), - /// } - /// ``` - /// - /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected - #[deprecated(since = "0.4.0", note = "use `nitrokey::Manager::connect_pro` instead")] - pub fn connect() -> Result { - crate::take()?.connect_pro().map_err(Into::into) - } - - pub(crate) fn new() -> Pro { - Pro { - marker: marker::PhantomData, - } +impl<'a> Pro<'a> { + pub(crate) fn new(manager: &'a mut crate::Manager) -> Pro<'a> { + Pro { manager } } } -impl Drop for Pro { +impl<'a> Drop for Pro<'a> { fn drop(&mut self) { unsafe { nitrokey_sys::NK_logout(); @@ -775,47 +725,17 @@ impl Drop for Pro { } } -impl Device for Pro { +impl<'a> Device for Pro<'a> { fn get_model(&self) -> Model { Model::Pro } } -impl GenerateOtp for Pro {} +impl<'a> GenerateOtp for Pro<'a> {} -impl Storage { - /// Connects to a Nitrokey Storage. - /// - /// # Errors - /// - /// - [`NotConnected`][] if no Nitrokey device of the given model is connected - /// - /// # Example - /// - /// ``` - /// use nitrokey::Storage; - /// - /// fn use_storage(device: Storage) {} - /// - /// match nitrokey::Storage::connect() { - /// Ok(device) => use_storage(device), - /// Err(err) => eprintln!("Could not connect to the Nitrokey Storage: {}", err), - /// } - /// ``` - /// - /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected - #[deprecated( - since = "0.4.0", - note = "use `nitrokey::Manager::connect_storage` instead" - )] - pub fn connect() -> Result { - crate::take()?.connect_storage().map_err(Into::into) - } - - pub(crate) fn new() -> Storage { - Storage { - marker: marker::PhantomData, - } +impl<'a> Storage<'a> { + pub(crate) fn new(manager: &'a mut crate::Manager) -> Storage<'a> { + Storage { manager } } /// Changes the update PIN. @@ -1320,7 +1240,7 @@ impl Storage { } } -impl Drop for Storage { +impl<'a> Drop for Storage<'a> { fn drop(&mut self) { unsafe { nitrokey_sys::NK_logout(); @@ -1328,13 +1248,13 @@ impl Drop for Storage { } } -impl Device for Storage { +impl<'a> Device for Storage<'a> { fn get_model(&self) -> Model { Model::Storage } } -impl GenerateOtp for Storage {} +impl<'a> GenerateOtp for Storage<'a> {} impl From for StorageProductionInfo { fn from(data: nitrokey_sys::NK_storage_ProductionTest) -> Self { diff --git a/src/lib.rs b/src/lib.rs index dc3432a..8b0aae5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,8 +110,8 @@ pub use crate::auth::{Admin, Authenticate, User}; pub use crate::config::Config; #[allow(deprecated)] pub use crate::device::{ - connect, Device, DeviceWrapper, Model, Pro, SdCardData, Storage, StorageProductionInfo, - StorageStatus, VolumeMode, VolumeStatus, + Device, DeviceWrapper, Model, Pro, SdCardData, Storage, StorageProductionInfo, StorageStatus, + VolumeMode, VolumeStatus, }; pub use crate::error::{CommandError, CommunicationError, Error, LibraryError}; pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData}; @@ -204,12 +204,9 @@ impl Manager { /// ``` /// /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected - pub fn connect(&mut self) -> Result { + pub fn connect(&mut self) -> Result, Error> { if unsafe { nitrokey_sys::NK_login_auto() } == 1 { - match device::get_connected_device() { - Some(wrapper) => Ok(wrapper), - None => Err(CommunicationError::NotConnected.into()), - } + device::get_connected_device(self) } else { Err(CommunicationError::NotConnected.into()) } @@ -239,9 +236,9 @@ impl Manager { /// ``` /// /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected - pub fn connect_model(&mut self, model: Model) -> Result { + pub fn connect_model(&mut self, model: Model) -> Result, Error> { if device::connect_enum(model) { - Ok(device::create_device_wrapper(model)) + Ok(device::create_device_wrapper(self, model)) } else { Err(CommunicationError::NotConnected.into()) } @@ -270,9 +267,9 @@ impl Manager { /// ``` /// /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected - pub fn connect_pro(&mut self) -> Result { + pub fn connect_pro(&mut self) -> Result, Error> { if device::connect_enum(device::Model::Pro) { - Ok(device::Pro::new()) + Ok(device::Pro::new(self)) } else { Err(CommunicationError::NotConnected.into()) } @@ -301,9 +298,9 @@ impl Manager { /// ``` /// /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected - pub fn connect_storage(&mut self) -> Result { + pub fn connect_storage(&mut self) -> Result, Error> { if device::connect_enum(Model::Storage) { - Ok(Storage::new()) + Ok(Storage::new(self)) } else { Err(CommunicationError::NotConnected.into()) } diff --git a/src/pws.rs b/src/pws.rs index 371de6e..cf2dd42 100644 --- a/src/pws.rs +++ b/src/pws.rs @@ -364,19 +364,19 @@ impl<'a> Drop for PasswordSafe<'a> { } } -impl GetPasswordSafe for Pro { +impl<'a> GetPasswordSafe for Pro<'a> { fn get_password_safe(&mut self, user_pin: &str) -> Result, Error> { get_password_safe(self, user_pin) } } -impl GetPasswordSafe for Storage { +impl<'a> GetPasswordSafe for Storage<'a> { fn get_password_safe(&mut self, user_pin: &str) -> Result, Error> { get_password_safe(self, user_pin) } } -impl GetPasswordSafe for DeviceWrapper { +impl<'a> GetPasswordSafe for DeviceWrapper<'a> { fn get_password_safe(&mut self, user_pin: &str) -> Result, Error> { get_password_safe(self, user_pin) } -- cgit v1.2.3 From 12fa62483cf45d868099d5d4020333af492eebde Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 9 Jul 2019 08:09:02 +0000 Subject: Introduce into_manager for Device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To enable applications like nitrokey-test to go back to a manager instance from a Device instance, we add the into_manager function to the Device trait. To do that, we have to keep track of the Manager’s lifetime by adding a lifetime to Device (and then to some other traits that use Device). --- CHANGELOG.md | 1 + src/auth.rs | 69 +++++++++++++++++++++++++++++++-------------------------- src/device.rs | 64 +++++++++++++++++++++++++++++++++++++++++++--------- src/error.rs | 2 +- src/pws.rs | 30 ++++++++++++------------- tests/device.rs | 16 ++++++++----- tests/otp.rs | 4 ++-- tests/pws.rs | 4 ++-- 8 files changed, 123 insertions(+), 67 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 88e68dd..06769bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ SPDX-License-Identifier: MIT - Add `ConcurrentAccessError` and `PoisonError` `Error` variants. - Add the `Manager` struct that manages connections to Nitrokey devices. - Remove `connect`, `connect_model`, `Pro::connect` and `Storage::connect`. + - Add the `into_manager` function to the `Device` trait. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/auth.rs b/src/auth.rs index 2ed7bfc..829d083 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,6 +1,7 @@ // Copyright (C) 2018-2019 Robin Krahl // SPDX-License-Identifier: MIT +use std::marker; use std::ops; use std::os::raw::c_char; use std::os::raw::c_int; @@ -18,7 +19,7 @@ static TEMPORARY_PASSWORD_LENGTH: usize = 25; /// Provides methods to authenticate as a user or as an admin using a PIN. The authenticated /// methods will consume the current device instance. On success, they return the authenticated /// device. Otherwise, they return the current unauthenticated device and the error code. -pub trait Authenticate { +pub trait Authenticate<'a> { /// Performs user authentication. This method consumes the device. If successful, an /// authenticated device is returned. Otherwise, the current unauthenticated device and the /// error are returned. @@ -61,9 +62,9 @@ pub trait Authenticate { /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`RngError`]: enum.CommandError.html#variant.RngError /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn authenticate_user(self, password: &str) -> Result, (Self, Error)> + fn authenticate_user(self, password: &str) -> Result, (Self, Error)> where - Self: Device + Sized; + Self: Device<'a> + Sized; /// Performs admin authentication. This method consumes the device. If successful, an /// authenticated device is returned. Otherwise, the current unauthenticated device and the @@ -107,9 +108,9 @@ pub trait Authenticate { /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`RngError`]: enum.CommandError.html#variant.RngError /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn authenticate_admin(self, password: &str) -> Result, (Self, Error)> + fn authenticate_admin(self, password: &str) -> Result, (Self, Error)> where - Self: Device + Sized; + Self: Device<'a> + Sized; } trait AuthenticatedDevice { @@ -128,9 +129,10 @@ trait AuthenticatedDevice { /// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin /// [`device`]: #method.device #[derive(Debug)] -pub struct User { +pub struct User<'a, T: Device<'a>> { device: T, temp_password: Vec, + marker: marker::PhantomData<&'a T>, } /// A Nitrokey device with admin authentication. @@ -143,14 +145,15 @@ pub struct User { /// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin /// [`device`]: #method.device #[derive(Debug)] -pub struct Admin { +pub struct Admin<'a, T: Device<'a>> { device: T, temp_password: Vec, + marker: marker::PhantomData<&'a T>, } -fn authenticate(device: D, password: &str, callback: T) -> Result +fn authenticate<'a, D, A, T>(device: D, password: &str, callback: T) -> Result where - D: Device, + D: Device<'a>, A: AuthenticatedDevice, T: Fn(*const c_char, *const c_char) -> c_int, { @@ -174,9 +177,9 @@ fn authenticate_user_wrapper<'a, T, C>( device: T, constructor: C, password: &str, -) -> Result>, (DeviceWrapper<'a>, Error)> +) -> Result>, (DeviceWrapper<'a>, Error)> where - T: Device, + T: Device<'a> + 'a, C: Fn(T) -> DeviceWrapper<'a>, { let result = device.authenticate_user(password); @@ -190,9 +193,9 @@ fn authenticate_admin_wrapper<'a, T, C>( device: T, constructor: C, password: &str, -) -> Result>, (DeviceWrapper<'a>, Error)> +) -> Result>, (DeviceWrapper<'a>, Error)> where - T: Device, + T: Device<'a> + 'a, C: Fn(T) -> DeviceWrapper<'a>, { let result = device.authenticate_admin(password); @@ -202,7 +205,7 @@ where } } -impl User { +impl<'a, T: Device<'a>> User<'a, T> { /// Forgets the user authentication and returns an unauthenticated device. This method /// consumes the authenticated device. It does not perform any actual commands on the /// Nitrokey. @@ -211,7 +214,7 @@ impl User { } } -impl ops::Deref for User { +impl<'a, T: Device<'a>> ops::Deref for User<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { @@ -219,13 +222,13 @@ impl ops::Deref for User { } } -impl ops::DerefMut for User { +impl<'a, T: Device<'a>> ops::DerefMut for User<'a, T> { fn deref_mut(&mut self) -> &mut T { &mut self.device } } -impl GenerateOtp for User { +impl<'a, T: Device<'a>> GenerateOtp for User<'a, T> { fn get_hotp_code(&mut self, slot: u8) -> Result { result_from_string(unsafe { nitrokey_sys::NK_get_hotp_code_PIN(slot, self.temp_password_ptr()) @@ -239,11 +242,12 @@ impl GenerateOtp for User { } } -impl AuthenticatedDevice for User { +impl<'a, T: Device<'a>> AuthenticatedDevice for User<'a, T> { fn new(device: T, temp_password: Vec) -> Self { User { device, temp_password, + marker: marker::PhantomData, } } @@ -252,7 +256,7 @@ impl AuthenticatedDevice for User { } } -impl ops::Deref for Admin { +impl<'a, T: Device<'a>> ops::Deref for Admin<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { @@ -260,13 +264,13 @@ impl ops::Deref for Admin { } } -impl ops::DerefMut for Admin { +impl<'a, T: Device<'a>> ops::DerefMut for Admin<'a, T> { fn deref_mut(&mut self) -> &mut T { &mut self.device } } -impl Admin { +impl<'a, T: Device<'a>> Admin<'a, T> { /// Forgets the user authentication and returns an unauthenticated device. This method /// consumes the authenticated device. It does not perform any actual commands on the /// Nitrokey. @@ -316,7 +320,7 @@ impl Admin { } } -impl ConfigureOtp for Admin { +impl<'a, T: Device<'a>> ConfigureOtp for Admin<'a, T> { fn write_hotp_slot(&mut self, data: OtpSlotData, counter: u64) -> Result<(), Error> { let raw_data = RawOtpSlotData::new(data)?; get_command_result(unsafe { @@ -364,11 +368,12 @@ impl ConfigureOtp for Admin { } } -impl AuthenticatedDevice for Admin { +impl<'a, T: Device<'a>> AuthenticatedDevice for Admin<'a, T> { fn new(device: T, temp_password: Vec) -> Self { Admin { device, temp_password, + marker: marker::PhantomData, } } @@ -377,8 +382,8 @@ impl AuthenticatedDevice for Admin { } } -impl<'a> Authenticate for DeviceWrapper<'a> { - fn authenticate_user(self, password: &str) -> Result, (Self, Error)> { +impl<'a> Authenticate<'a> for DeviceWrapper<'a> { + fn authenticate_user(self, password: &str) -> Result, (Self, Error)> { match self { DeviceWrapper::Storage(storage) => { authenticate_user_wrapper(storage, DeviceWrapper::Storage, password) @@ -387,7 +392,7 @@ impl<'a> Authenticate for DeviceWrapper<'a> { } } - fn authenticate_admin(self, password: &str) -> Result, (Self, Error)> { + fn authenticate_admin(self, password: &str) -> Result, (Self, Error)> { match self { DeviceWrapper::Storage(storage) => { authenticate_admin_wrapper(storage, DeviceWrapper::Storage, password) @@ -399,28 +404,28 @@ impl<'a> Authenticate for DeviceWrapper<'a> { } } -impl<'a> Authenticate for Pro<'a> { - fn authenticate_user(self, password: &str) -> Result, (Self, Error)> { +impl<'a> Authenticate<'a> for Pro<'a> { + fn authenticate_user(self, password: &str) -> Result, (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, (Self, Error)> { + fn authenticate_admin(self, password: &str) -> Result, (Self, Error)> { authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr) }) } } -impl<'a> Authenticate for Storage<'a> { - fn authenticate_user(self, password: &str) -> Result, (Self, Error)> { +impl<'a> Authenticate<'a> for Storage<'a> { + fn authenticate_user(self, password: &str) -> Result, (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, (Self, Error)> { + fn authenticate_admin(self, password: &str) -> Result, (Self, Error)> { authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr) }) diff --git a/src/device.rs b/src/device.rs index 50ff071..e1a71fa 100644 --- a/src/device.rs +++ b/src/device.rs @@ -156,7 +156,7 @@ pub enum DeviceWrapper<'a> { /// [`Pro::connect`]: #method.connect #[derive(Debug)] pub struct Pro<'a> { - manager: &'a mut crate::Manager, + manager: Option<&'a mut crate::Manager>, } /// A Nitrokey Storage device without user or admin authentication. @@ -200,7 +200,7 @@ pub struct Pro<'a> { /// [`Storage::connect`]: #method.connect #[derive(Debug)] pub struct Storage<'a> { - manager: &'a mut crate::Manager, + manager: Option<&'a mut crate::Manager>, } /// The status of a volume on a Nitrokey Storage device. @@ -291,7 +291,32 @@ pub struct StorageStatus { /// /// This trait provides the commands that can be executed without authentication and that are /// present on all supported Nitrokey devices. -pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug { +pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt::Debug { + /// Returns the [`Manager`][] instance that has been used to connect to this device. + /// + /// # Example + /// + /// ``` + /// use nitrokey::{Device, DeviceWrapper}; + /// + /// fn do_something(device: DeviceWrapper) { + /// // reconnect to any device + /// let manager = device.into_manager(); + /// let device = manager.connect(); + /// // do something with the device + /// // ... + /// } + /// + /// # fn main() -> Result<(), nitrokey::Error> { + /// match nitrokey::take()?.connect() { + /// Ok(device) => do_something(device), + /// Err(err) => println!("Could not connect to a Nitrokey: {}", err), + /// } + /// # Ok(()) + /// # } + /// ``` + fn into_manager(self) -> &'a mut crate::Manager; + /// Returns the model of the connected Nitrokey device. /// /// # Example @@ -657,14 +682,14 @@ pub(crate) fn connect_enum(model: Model) -> bool { } impl<'a> DeviceWrapper<'a> { - fn device(&self) -> &dyn Device { + fn device(&self) -> &dyn Device<'a> { match *self { DeviceWrapper::Storage(ref storage) => storage, DeviceWrapper::Pro(ref pro) => pro, } } - fn device_mut(&mut self) -> &mut dyn Device { + fn device_mut(&mut self) -> &mut dyn Device<'a> { match *self { DeviceWrapper::Storage(ref mut storage) => storage, DeviceWrapper::Pro(ref mut pro) => pro, @@ -702,7 +727,14 @@ impl<'a> GenerateOtp for DeviceWrapper<'a> { } } -impl<'a> Device for DeviceWrapper<'a> { +impl<'a> Device<'a> for DeviceWrapper<'a> { + fn into_manager(self) -> &'a mut crate::Manager { + match self { + DeviceWrapper::Pro(dev) => dev.into_manager(), + DeviceWrapper::Storage(dev) => dev.into_manager(), + } + } + fn get_model(&self) -> Model { match *self { DeviceWrapper::Pro(_) => Model::Pro, @@ -713,7 +745,9 @@ impl<'a> Device for DeviceWrapper<'a> { impl<'a> Pro<'a> { pub(crate) fn new(manager: &'a mut crate::Manager) -> Pro<'a> { - Pro { manager } + Pro { + manager: Some(manager), + } } } @@ -725,7 +759,11 @@ impl<'a> Drop for Pro<'a> { } } -impl<'a> Device for Pro<'a> { +impl<'a> Device<'a> for Pro<'a> { + fn into_manager(mut self) -> &'a mut crate::Manager { + self.manager.take().unwrap() + } + fn get_model(&self) -> Model { Model::Pro } @@ -735,7 +773,9 @@ impl<'a> GenerateOtp for Pro<'a> {} impl<'a> Storage<'a> { pub(crate) fn new(manager: &'a mut crate::Manager) -> Storage<'a> { - Storage { manager } + Storage { + manager: Some(manager), + } } /// Changes the update PIN. @@ -1248,7 +1288,11 @@ impl<'a> Drop for Storage<'a> { } } -impl<'a> Device for Storage<'a> { +impl<'a> Device<'a> for Storage<'a> { + fn into_manager(mut self) -> &'a mut crate::Manager { + self.manager.take().unwrap() + } + fn get_model(&self) -> Model { Model::Storage } diff --git a/src/error.rs b/src/error.rs index b84f5eb..9e6adc0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -85,7 +85,7 @@ impl From>> for Err } } -impl From<(T, Error)> for Error { +impl<'a, T: device::Device<'a>> From<(T, Error)> for Error { fn from((_, err): (T, Error)) -> Self { err } diff --git a/src/pws.rs b/src/pws.rs index cf2dd42..778765d 100644 --- a/src/pws.rs +++ b/src/pws.rs @@ -57,8 +57,8 @@ pub const SLOT_COUNT: u8 = 16; /// [`lock`]: trait.Device.html#method.lock /// [`GetPasswordSafe`]: trait.GetPasswordSafe.html #[derive(Debug)] -pub struct PasswordSafe<'a> { - _device: &'a dyn Device, +pub struct PasswordSafe<'a, 'b> { + _device: &'a dyn Device<'b>, } /// Provides access to a [`PasswordSafe`][]. @@ -67,7 +67,7 @@ pub struct PasswordSafe<'a> { /// retrieved from it. /// /// [`PasswordSafe`]: struct.PasswordSafe.html -pub trait GetPasswordSafe { +pub trait GetPasswordSafe<'a> { /// Enables and returns the password safe. /// /// The underlying device must always live at least as long as a password safe retrieved from @@ -117,13 +117,13 @@ pub trait GetPasswordSafe { /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString /// [`Unknown`]: enum.CommandError.html#variant.Unknown /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn get_password_safe(&mut self, user_pin: &str) -> Result, Error>; + fn get_password_safe(&mut self, user_pin: &str) -> Result, Error>; } -fn get_password_safe<'a>( - device: &'a dyn Device, +fn get_password_safe<'a, 'b>( + device: &'a dyn Device<'b>, user_pin: &str, -) -> Result, Error> { +) -> Result, Error> { let user_pin_string = get_cstring(user_pin)?; get_command_result(unsafe { nitrokey_sys::NK_enable_password_safe(user_pin_string.as_ptr()) }) .map(|_| PasswordSafe { _device: device }) @@ -137,7 +137,7 @@ fn get_pws_result(s: String) -> Result { } } -impl<'a> PasswordSafe<'a> { +impl<'a, 'b> PasswordSafe<'a, 'b> { /// Returns the status of all password slots. /// /// The status indicates whether a slot is programmed or not. @@ -357,27 +357,27 @@ impl<'a> PasswordSafe<'a> { } } -impl<'a> Drop for PasswordSafe<'a> { +impl<'a, 'b> Drop for PasswordSafe<'a, 'b> { fn drop(&mut self) { // TODO: disable the password safe -- NK_lock_device has side effects on the Nitrokey // Storage, see https://github.com/Nitrokey/nitrokey-storage-firmware/issues/65 } } -impl<'a> GetPasswordSafe for Pro<'a> { - fn get_password_safe(&mut self, user_pin: &str) -> Result, Error> { +impl<'a> GetPasswordSafe<'a> for Pro<'a> { + fn get_password_safe(&mut self, user_pin: &str) -> Result, Error> { get_password_safe(self, user_pin) } } -impl<'a> GetPasswordSafe for Storage<'a> { - fn get_password_safe(&mut self, user_pin: &str) -> Result, Error> { +impl<'a> GetPasswordSafe<'a> for Storage<'a> { + fn get_password_safe(&mut self, user_pin: &str) -> Result, Error> { get_password_safe(self, user_pin) } } -impl<'a> GetPasswordSafe for DeviceWrapper<'a> { - fn get_password_safe(&mut self, user_pin: &str) -> Result, Error> { +impl<'a> GetPasswordSafe<'a> for DeviceWrapper<'a> { + fn get_password_safe(&mut self, user_pin: &str) -> Result, Error> { get_password_safe(self, user_pin) } } diff --git a/tests/device.rs b/tests/device.rs index b377f2e..76f38e6 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -101,7 +101,10 @@ fn get_firmware_version(device: Pro) { assert!(version.minor > 0); } -fn admin_retry(device: T, suffix: &str, count: u8) -> T { +fn admin_retry<'a, T>(device: T, suffix: &str, count: u8) -> T +where + T: Authenticate<'a> + Device<'a> + 'a, +{ let result = device.authenticate_admin(&(DEFAULT_ADMIN_PIN.to_owned() + suffix)); let device = match result { Ok(admin) => admin.device(), @@ -111,7 +114,10 @@ fn admin_retry(device: T, suffix: &str, count: u8) -> return device; } -fn user_retry(device: T, suffix: &str, count: u8) -> T { +fn user_retry<'a, T>(device: T, suffix: &str, count: u8) -> T +where + T: Authenticate<'a> + Device<'a> + 'a, +{ let result = device.authenticate_user(&(DEFAULT_USER_PIN.to_owned() + suffix)); let device = match result { Ok(admin) => admin.device(), @@ -220,10 +226,10 @@ fn change_admin_pin(device: DeviceWrapper) { device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err(); } -fn require_failed_user_login(device: D, password: &str, error: CommandError) -> D +fn require_failed_user_login<'a, D>(device: D, password: &str, error: CommandError) -> D where - D: Device + Authenticate, - nitrokey::User: std::fmt::Debug, + D: Device<'a> + Authenticate<'a> + 'a, + nitrokey::User<'a, D>: std::fmt::Debug, { let result = device.authenticate_user(password); assert!(result.is_err()); diff --git a/tests/otp.rs b/tests/otp.rs index c0bbecf..aafda59 100644 --- a/tests/otp.rs +++ b/tests/otp.rs @@ -36,9 +36,9 @@ enum TotpTimestampSize { U64, } -fn make_admin_test_device(device: T) -> Admin +fn make_admin_test_device<'a, T>(device: T) -> Admin<'a, T> where - T: Device, + T: Device<'a>, (T, nitrokey::Error): Debug, { unwrap_ok!(device.authenticate_admin(DEFAULT_ADMIN_PIN)) diff --git a/tests/pws.rs b/tests/pws.rs index b0e5abe..7169695 100644 --- a/tests/pws.rs +++ b/tests/pws.rs @@ -32,9 +32,9 @@ fn get_slot_name_direct(slot: u8) -> Result { } } -fn get_pws(device: &mut T) -> PasswordSafe +fn get_pws<'a, T>(device: &mut T) -> PasswordSafe<'_, 'a> where - T: Device, + T: Device<'a>, { unwrap_ok!(device.get_password_safe(DEFAULT_USER_PIN)) } -- cgit v1.2.3 From 62e8ee8f5d02511d6eb5dc179b087b04e88c1b94 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 27 Jan 2019 19:52:53 +0000 Subject: Update documentation for Manager refactoring This patch updates the documentation to reflect the latest changes to connection handling. It also updates the doc tests to prefer the new methods over the old ones. --- src/auth.rs | 13 ++++--- src/device.rs | 117 ++++++++++++++++++++++++++++++++++++---------------------- src/lib.rs | 77 +++++++++++++++++++++++++++++++------- src/otp.rs | 27 +++++++++----- src/pws.rs | 24 ++++++++---- 5 files changed, 178 insertions(+), 80 deletions(-) (limited to 'src') diff --git a/src/auth.rs b/src/auth.rs index 829d083..0b000f7 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -39,11 +39,12 @@ pub trait Authenticate<'a> { /// use nitrokey::{Authenticate, DeviceWrapper, User}; /// # use nitrokey::Error; /// - /// fn perform_user_task(device: &User) {} + /// fn perform_user_task<'a>(device: &User<'a, DeviceWrapper<'a>>) {} /// fn perform_other_task(device: &DeviceWrapper) {} /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// let device = match device.authenticate_user("123456") { /// Ok(user) => { /// perform_user_task(&user); @@ -85,11 +86,12 @@ pub trait Authenticate<'a> { /// use nitrokey::{Authenticate, Admin, DeviceWrapper}; /// # use nitrokey::Error; /// - /// fn perform_admin_task(device: &Admin) {} + /// fn perform_admin_task<'a>(device: &Admin<'a, DeviceWrapper<'a>>) {} /// fn perform_other_task(device: &DeviceWrapper) {} /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// let device = match device.authenticate_admin("123456") { /// Ok(admin) => { /// perform_admin_task(&admin); @@ -291,7 +293,8 @@ impl<'a, T: Device<'a>> Admin<'a, T> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// let config = Config::new(None, None, None, false); /// match device.authenticate_admin("12345678") { /// Ok(mut admin) => { diff --git a/src/device.rs b/src/device.rs index e1a71fa..758d4c1 100644 --- a/src/device.rs +++ b/src/device.rs @@ -53,7 +53,7 @@ impl fmt::Display for VolumeMode { /// A wrapper for a Nitrokey device of unknown type. /// -/// Use the function [`connect`][] to obtain a wrapped instance. The wrapper implements all traits +/// Use the [`connect`][] method to obtain a wrapped instance. The wrapper implements all traits /// that are shared between all Nitrokey devices so that the shared functionality can be used /// without knowing the type of the underlying device. If you want to use functionality that is /// not available for all devices, you have to extract the device. @@ -66,11 +66,12 @@ impl fmt::Display for VolumeMode { /// use nitrokey::{Authenticate, DeviceWrapper, User}; /// # use nitrokey::Error; /// -/// fn perform_user_task(device: &User) {} +/// fn perform_user_task<'a>(device: &User<'a, DeviceWrapper<'a>>) {} /// fn perform_other_task(device: &DeviceWrapper) {} /// /// # fn try_main() -> Result<(), Error> { -/// let device = nitrokey::connect()?; +/// let mut manager = nitrokey::take()?; +/// let device = manager.connect()?; /// let device = match device.authenticate_user("123456") { /// Ok(user) => { /// perform_user_task(&user); @@ -96,7 +97,8 @@ impl fmt::Display for VolumeMode { /// fn perform_storage_task(device: &Storage) {} /// /// # fn try_main() -> Result<(), Error> { -/// let device = nitrokey::connect()?; +/// let mut manager = nitrokey::take()?; +/// let device = manager.connect()?; /// perform_common_task(&device); /// match device { /// DeviceWrapper::Storage(storage) => perform_storage_task(&storage), @@ -106,7 +108,7 @@ impl fmt::Display for VolumeMode { /// # } /// ``` /// -/// [`connect`]: fn.connect.html +/// [`connect`]: struct.Manager.html#method.connect #[derive(Debug)] pub enum DeviceWrapper<'a> { /// A Nitrokey Storage device. @@ -117,10 +119,9 @@ pub enum DeviceWrapper<'a> { /// A Nitrokey Pro device without user or admin authentication. /// -/// Use the global function [`connect`][] to obtain an instance wrapper or the method -/// [`connect`][`Pro::connect`] to directly obtain an instance. If you want to execute a command -/// that requires user or admin authentication, use [`authenticate_admin`][] or -/// [`authenticate_user`][]. +/// Use the [`connect`][] method to obtain an instance wrapper or the [`connect_pro`] method to +/// directly obtain an instance. If you want to execute a command that requires user or admin +/// authentication, use [`authenticate_admin`][] or [`authenticate_user`][]. /// /// # Examples /// @@ -130,11 +131,12 @@ pub enum DeviceWrapper<'a> { /// use nitrokey::{Authenticate, User, Pro}; /// # use nitrokey::Error; /// -/// fn perform_user_task(device: &User) {} +/// fn perform_user_task<'a>(device: &User<'a, Pro<'a>>) {} /// fn perform_other_task(device: &Pro) {} /// /// # fn try_main() -> Result<(), Error> { -/// let device = nitrokey::Pro::connect()?; +/// let mut manager = nitrokey::take()?; +/// let device = manager.connect_pro()?; /// let device = match device.authenticate_user("123456") { /// Ok(user) => { /// perform_user_task(&user); @@ -152,8 +154,8 @@ pub enum DeviceWrapper<'a> { /// /// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin /// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user -/// [`connect`]: fn.connect.html -/// [`Pro::connect`]: #method.connect +/// [`connect`]: struct.Manager.html#method.connect +/// [`connect_pro`]: struct.Manager.html#method.connect_pro #[derive(Debug)] pub struct Pro<'a> { manager: Option<&'a mut crate::Manager>, @@ -161,10 +163,9 @@ pub struct Pro<'a> { /// A Nitrokey Storage device without user or admin authentication. /// -/// Use the global function [`connect`][] to obtain an instance wrapper or the method -/// [`connect`][`Storage::connect`] to directly obtain an instance. If you want to execute a -/// command that requires user or admin authentication, use [`authenticate_admin`][] or -/// [`authenticate_user`][]. +/// Use the [`connect`][] method to obtain an instance wrapper or the [`connect_storage`] method to +/// directly obtain an instance. If you want to execute a command that requires user or admin +/// authentication, use [`authenticate_admin`][] or [`authenticate_user`][]. /// /// # Examples /// @@ -174,11 +175,12 @@ pub struct Pro<'a> { /// use nitrokey::{Authenticate, User, Storage}; /// # use nitrokey::Error; /// -/// fn perform_user_task(device: &User) {} +/// fn perform_user_task<'a>(device: &User<'a, Storage<'a>>) {} /// fn perform_other_task(device: &Storage) {} /// /// # fn try_main() -> Result<(), Error> { -/// let device = nitrokey::Storage::connect()?; +/// let mut manager = nitrokey::take()?; +/// let device = manager.connect_storage()?; /// let device = match device.authenticate_user("123456") { /// Ok(user) => { /// perform_user_task(&user); @@ -196,8 +198,8 @@ pub struct Pro<'a> { /// /// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin /// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user -/// [`connect`]: fn.connect.html -/// [`Storage::connect`]: #method.connect +/// [`connect`]: struct.Manager.html#method.connect +/// [`connect_storage`]: struct.Manager.html#method.connect_storage #[derive(Debug)] pub struct Storage<'a> { manager: Option<&'a mut crate::Manager>, @@ -326,7 +328,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// println!("Connected to a Nitrokey {}", device.get_model()); /// # Ok(()) /// # } @@ -342,7 +345,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// match device.get_serial_number() { /// Ok(number) => println!("serial no: {}", number), /// Err(err) => eprintln!("Could not get serial number: {}", err), @@ -364,7 +368,9 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; + /// let count = device.get_user_retry_count(); /// match device.get_user_retry_count() { /// Ok(count) => println!("{} remaining authentication attempts (user)", count), /// Err(err) => eprintln!("Could not get user retry count: {}", err), @@ -386,7 +392,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// let count = device.get_admin_retry_count(); /// match device.get_admin_retry_count() { /// Ok(count) => println!("{} remaining authentication attempts (admin)", count), @@ -408,7 +415,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// match device.get_firmware_version() { /// Ok(version) => println!("Firmware version: {}", version), /// Err(err) => eprintln!("Could not access firmware version: {}", err), @@ -431,7 +439,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// let config = device.get_config()?; /// println!("numlock binding: {:?}", config.numlock); /// println!("capslock binding: {:?}", config.capslock); @@ -465,7 +474,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// match device.change_admin_pin("12345678", "12345679") { /// Ok(()) => println!("Updated admin PIN."), /// Err(err) => eprintln!("Failed to update admin PIN: {}", err), @@ -498,7 +508,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// match device.change_user_pin("123456", "123457") { /// Ok(()) => println!("Updated admin PIN."), /// Err(err) => eprintln!("Failed to update admin PIN: {}", err), @@ -531,7 +542,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// match device.unlock_user_pin("12345678", "123456") { /// Ok(()) => println!("Unlocked user PIN."), /// Err(err) => eprintln!("Failed to unlock user PIN: {}", err), @@ -565,7 +577,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// match device.lock() { /// Ok(()) => println!("Locked the Nitrokey device."), /// Err(err) => eprintln!("Could not lock the Nitrokey device: {}", err), @@ -596,7 +609,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// match device.factory_reset("12345678") { /// Ok(()) => println!("Performed a factory reset."), /// Err(err) => eprintln!("Could not perform a factory reset: {}", err), @@ -630,7 +644,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// match device.build_aes_key("12345678") { /// Ok(()) => println!("New AES keys have been built."), /// Err(err) => eprintln!("Could not build new AES keys: {}", err), @@ -795,7 +810,8 @@ impl<'a> Storage<'a> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// match device.change_update_pin("12345678", "87654321") { /// Ok(()) => println!("Updated update PIN."), /// Err(err) => eprintln!("Failed to update update PIN: {}", err), @@ -832,7 +848,8 @@ impl<'a> Storage<'a> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// match device.enable_firmware_update("12345678") { /// Ok(()) => println!("Nitrokey entered update mode."), /// Err(err) => eprintln!("Could not enter update mode: {}", err), @@ -866,7 +883,8 @@ impl<'a> Storage<'a> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// match device.enable_encrypted_volume("123456") { /// Ok(()) => println!("Enabled the encrypted volume."), /// Err(err) => eprintln!("Could not enable the encrypted volume: {}", err), @@ -895,7 +913,8 @@ impl<'a> Storage<'a> { /// fn use_volume() {} /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// match device.enable_encrypted_volume("123456") { /// Ok(()) => { /// println!("Enabled the encrypted volume."); @@ -941,7 +960,8 @@ impl<'a> Storage<'a> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// device.enable_encrypted_volume("123445")?; /// match device.enable_hidden_volume("hidden-pw") { /// Ok(()) => println!("Enabled a hidden volume."), @@ -974,7 +994,8 @@ impl<'a> Storage<'a> { /// fn use_volume() {} /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// device.enable_encrypted_volume("123445")?; /// match device.enable_hidden_volume("hidden-pw") { /// Ok(()) => { @@ -1021,7 +1042,8 @@ impl<'a> Storage<'a> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// device.enable_encrypted_volume("123445")?; /// device.create_hidden_volume(0, 0, 100, "hidden-pw")?; /// # Ok(()) @@ -1061,7 +1083,8 @@ impl<'a> Storage<'a> { /// use nitrokey::VolumeMode; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// match device.set_unencrypted_volume_mode("12345678", VolumeMode::ReadWrite) { /// Ok(()) => println!("Set the unencrypted volume to read-write mode."), /// Err(err) => eprintln!("Could not set the unencrypted volume to read-write mode: {}", err), @@ -1106,7 +1129,8 @@ impl<'a> Storage<'a> { /// use nitrokey::VolumeMode; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// match device.set_encrypted_volume_mode("12345678", VolumeMode::ReadWrite) { /// Ok(()) => println!("Set the encrypted volume to read-write mode."), /// Err(err) => eprintln!("Could not set the encrypted volume to read-write mode: {}", err), @@ -1144,7 +1168,8 @@ impl<'a> Storage<'a> { /// fn use_volume() {} /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect_storage()?; /// match device.get_status() { /// Ok(status) => { /// println!("SD card ID: {:#x}", status.serial_number_sd_card); @@ -1187,7 +1212,8 @@ impl<'a> Storage<'a> { /// fn use_volume() {} /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect_storage()?; /// match device.get_production_info() { /// Ok(data) => { /// println!("SD card ID: {:#x}", data.sd_card.serial_number); @@ -1235,7 +1261,8 @@ impl<'a> Storage<'a> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::Storage::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; /// match device.clear_new_sd_card_warning("12345678") { /// Ok(()) => println!("Cleared the new SD card warning."), /// Err(err) => eprintln!("Could not set the clear the new SD card warning: {}", err), diff --git a/src/lib.rs b/src/lib.rs index 8b0aae5..36a1dfd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,13 +9,16 @@ //! performed without authentication, some require user access, and some require admin access. //! This is modelled using the types [`User`][] and [`Admin`][]. //! -//! Use [`connect`][] to connect to any Nitrokey device. The method will return a +//! You can only connect to one Nitrokey at a time. Use the global [`take`][] function to obtain +//! an reference to the [`Manager`][] singleton that keeps track of the connections. Then use the +//! [`connect`][] method to connect to any Nitrokey device. The method will return a //! [`DeviceWrapper`][] that abstracts over the supported Nitrokey devices. You can also use -//! [`Pro::connect`][] or [`Storage::connect`][] to connect to a specific device. +//! [`connect_model`][], [`connect_pro`][] or [`connect_storage`][] to connect to a specific +//! device. //! -//! You can then use [`authenticate_user`][] or [`authenticate_admin`][] to get an authenticated -//! device that can perform operations that require authentication. You can use [`device`][] to go -//! back to the unauthenticated device. +//! You can call [`authenticate_user`][] or [`authenticate_admin`][] to get an authenticated device +//! that can perform operations that require authentication. You can use [`device`][] to go back +//! to the unauthenticated device. //! //! This makes sure that you can only execute a command if you have the required access rights. //! Otherwise, your code will not compile. The only exception are the methods to generate one-time @@ -31,7 +34,8 @@ //! # use nitrokey::Error; //! //! # fn try_main() -> Result<(), Error> { -//! let device = nitrokey::connect()?; +//! let mut manager = nitrokey::take()?; +//! let device = manager.connect()?; //! println!("{}", device.get_serial_number()?); //! # Ok(()) //! # } @@ -44,7 +48,8 @@ //! # use nitrokey::Error; //! //! # fn try_main() -> Result<(), Error> { -//! let device = nitrokey::connect()?; +//! let mut manager = nitrokey::take()?; +//! let device = manager.connect()?; //! let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits); //! match device.authenticate_admin("12345678") { //! Ok(mut admin) => { @@ -66,7 +71,8 @@ //! # use nitrokey::Error; //! //! # fn try_main() -> Result<(), Error> { -//! let mut device = nitrokey::connect()?; +//! let mut manager = nitrokey::take()?; +//! let mut device = manager.connect()?; //! match device.get_hotp_code(1) { //! Ok(code) => println!("Generated HOTP code: {}", code), //! Err(err) => eprintln!("Could not generate HOTP code: {}", err), @@ -77,9 +83,12 @@ //! //! [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin //! [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user -//! [`connect`]: fn.connect.html -//! [`Pro::connect`]: struct.Pro.html#fn.connect.html -//! [`Storage::connect`]: struct.Storage.html#fn.connect.html +//! [`take`]: fn.take.html +//! [`connect`]: struct.Manager.html#method.connect +//! [`connect_model`]: struct.Manager.html#method.connect_model +//! [`connect_pro`]: struct.Manager.html#method.connect_pro +//! [`connect_storage`]: struct.Manager.html#method.connect_storage +//! [`manager`]: trait.Device.html#method.manager //! [`device`]: struct.User.html#method.device //! [`get_hotp_code`]: trait.GenerateOtp.html#method.get_hotp_code //! [`get_totp_code`]: trait.GenerateOtp.html#method.get_totp_code @@ -163,9 +172,50 @@ impl fmt::Display for Version { /// manager struct makes sure that `nitrokey-rs` does not try to connect to two devices at the same /// time. /// -/// To obtain an instance of this manager, use the [`take`][] function. +/// To obtain a reference to an instance of this manager, use the [`take`][] function. Use one of +/// the connect methods – [`connect`][], [`connect_model`][], [`connect_pro`][] or +/// [`connect_storage`][] – to retrieve a [`Device`][] instance. /// +/// # Examples +/// +/// Connect to a single device: +/// +/// ```no_run +/// use nitrokey::Device; +/// # use nitrokey::Error; +/// +/// # fn try_main() -> Result<(), Error> { +/// let mut manager = nitrokey::take()?; +/// let device = manager.connect()?; +/// println!("{}", device.get_serial_number()?); +/// # Ok(()) +/// # } +/// ``` +/// +/// Connect to a Pro and a Storage device: +/// +/// ```no_run +/// use nitrokey::{Device, Model}; +/// # use nitrokey::Error; +/// +/// # fn try_main() -> Result<(), Error> { +/// let mut manager = nitrokey::take()?; +/// let device = manager.connect_model(Model::Pro)?; +/// println!("Pro: {}", device.get_serial_number()?); +/// drop(device); +/// let device = manager.connect_model(Model::Storage)?; +/// println!("Storage: {}", device.get_serial_number()?); +/// # Ok(()) +/// # } +/// ``` +/// +/// [`connect`]: #method.connect +/// [`connect_model`]: #method.connect_model +/// [`connect_pro`]: #method.connect_pro +/// [`connect_storage`]: #method.connect_storage +/// [`manager`]: trait.Device.html#method.manager /// [`take`]: fn.take.html +/// [`Device`]: trait.Device.html #[derive(Debug)] pub struct Manager { marker: marker::PhantomData<()>, @@ -195,7 +245,8 @@ impl Manager { /// fn do_something(device: DeviceWrapper) {} /// /// # fn main() -> Result<(), nitrokey::Error> { - /// match nitrokey::take()?.connect() { + /// let mut manager = nitrokey::take()?; + /// match manager.connect() { /// Ok(device) => do_something(device), /// Err(err) => println!("Could not connect to a Nitrokey: {}", err), /// } diff --git a/src/otp.rs b/src/otp.rs index ee142c7..4667aff 100644 --- a/src/otp.rs +++ b/src/otp.rs @@ -35,7 +35,8 @@ pub trait ConfigureOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits); /// match device.authenticate_admin("12345678") { /// Ok(mut admin) => { @@ -71,7 +72,8 @@ pub trait ConfigureOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::EightDigits); /// match device.authenticate_admin("12345678") { /// Ok(mut admin) => { @@ -104,7 +106,8 @@ pub trait ConfigureOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// match device.authenticate_admin("12345678") { /// Ok(mut admin) => { /// match admin.erase_hotp_slot(1) { @@ -134,7 +137,8 @@ pub trait ConfigureOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// match device.authenticate_admin("12345678") { /// Ok(mut admin) => { /// match admin.erase_totp_slot(1) { @@ -171,7 +175,8 @@ pub trait GenerateOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH); /// match time { /// Ok(time) => device.set_time(time.as_secs(), false)?, @@ -209,7 +214,8 @@ pub trait GenerateOtp { /// use nitrokey::{CommandError, Error, GenerateOtp}; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// match device.get_hotp_slot_name(1) { /// Ok(name) => println!("HOTP slot 1: {}", name), /// Err(Error::CommandError(CommandError::SlotNotProgrammed)) => eprintln!("HOTP slot 1 not programmed"), @@ -238,7 +244,8 @@ pub trait GenerateOtp { /// use nitrokey::{CommandError, Error, GenerateOtp}; /// /// # fn try_main() -> Result<(), Error> { - /// let device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; /// match device.get_totp_slot_name(1) { /// Ok(name) => println!("TOTP slot 1: {}", name), /// Err(Error::CommandError(CommandError::SlotNotProgrammed)) => eprintln!("TOTP slot 1 not programmed"), @@ -270,7 +277,8 @@ pub trait GenerateOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// let code = device.get_hotp_code(1)?; /// println!("Generated HOTP code on slot 1: {}", code); /// # Ok(()) @@ -305,7 +313,8 @@ pub trait GenerateOtp { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH); /// match time { /// Ok(time) => { diff --git a/src/pws.rs b/src/pws.rs index 778765d..3398deb 100644 --- a/src/pws.rs +++ b/src/pws.rs @@ -43,7 +43,8 @@ pub const SLOT_COUNT: u8 = 16; /// } /// /// # fn try_main() -> Result<(), Error> { -/// let mut device = nitrokey::connect()?; +/// let mut manager = nitrokey::take()?; +/// let mut device = manager.connect()?; /// let pws = device.get_password_safe("123456")?; /// use_password_safe(&pws); /// drop(pws); @@ -98,7 +99,8 @@ pub trait GetPasswordSafe<'a> { /// fn use_password_safe(pws: &PasswordSafe) {} /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// match device.get_password_safe("123456") { /// Ok(pws) => { /// use_password_safe(&pws); @@ -149,7 +151,8 @@ impl<'a, 'b> PasswordSafe<'a, 'b> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// let pws = device.get_password_safe("123456")?; /// pws.get_slot_status()?.iter().enumerate().for_each(|(slot, programmed)| { /// let status = match *programmed { @@ -194,7 +197,8 @@ impl<'a, 'b> PasswordSafe<'a, 'b> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// match device.get_password_safe("123456") { /// Ok(pws) => { /// let name = pws.get_slot_name(0)?; @@ -231,7 +235,8 @@ impl<'a, 'b> PasswordSafe<'a, 'b> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// let pws = device.get_password_safe("123456")?; /// let name = pws.get_slot_name(0)?; /// let login = pws.get_slot_login(0)?; @@ -264,7 +269,8 @@ impl<'a, 'b> PasswordSafe<'a, 'b> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// let pws = device.get_password_safe("123456")?; /// let name = pws.get_slot_name(0)?; /// let login = pws.get_slot_login(0)?; @@ -295,7 +301,8 @@ impl<'a, 'b> PasswordSafe<'a, 'b> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// let pws = device.get_password_safe("123456")?; /// let name = pws.get_slot_name(0)?; /// let login = pws.get_slot_login(0)?; @@ -341,7 +348,8 @@ impl<'a, 'b> PasswordSafe<'a, 'b> { /// # use nitrokey::Error; /// /// # fn try_main() -> Result<(), Error> { - /// let mut device = nitrokey::connect()?; + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; /// let mut pws = device.get_password_safe("123456")?; /// match pws.erase_slot(0) { /// Ok(()) => println!("Erased slot 0."), -- cgit v1.2.3 From 88b32f5c2187e59fece93cd245aeadb4e5f9e04a Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 9 Jul 2019 10:22:08 +0000 Subject: Remove allow(deprecated) attribute for in lib.rs During the connection manager refactoring, we temporarily used deprecated methods. This is no longer the case, so we can remove the allow(deprecated) attribute. --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 36a1dfd..4e45877 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,7 +117,6 @@ use nitrokey_sys; pub use crate::auth::{Admin, Authenticate, User}; pub use crate::config::Config; -#[allow(deprecated)] pub use crate::device::{ Device, DeviceWrapper, Model, Pro, SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus, -- cgit v1.2.3 From 5e8f0fbaf6df0cb919e4b02401cc21d5280bf09c Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 9 Jul 2019 10:44:45 +0000 Subject: Add force_take function to ignore poisoned cache The take and take_blocking functions return a PoisonError if the cache is poisoned, i. e. if a thread panicked while holding the manager. This is a sensible default behaviour, but for example during testing, one might want to ignore the poisoned cache. This patch adds the force_take function that unwraps the PoisonError and returns the cached Manager even if the cache was poisoned. --- CHANGELOG.md | 2 ++ src/lib.rs | 32 +++++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 06769bd..b779929 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,8 @@ SPDX-License-Identifier: MIT - Add the `Manager` struct that manages connections to Nitrokey devices. - Remove `connect`, `connect_model`, `Pro::connect` and `Storage::connect`. - Add the `into_manager` function to the `Device` trait. + - Add the `force_take` function that ignores a `PoisonError` when accessing + the manager instance. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/lib.rs b/src/lib.rs index 4e45877..a4402c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -377,7 +377,8 @@ pub fn take_blocking() -> Result, Error> { /// /// There may only be one [`Manager`][] instance at the same time. If there already is an /// instance, a [`ConcurrentAccessError`][] is returned. If you want a blocking version, use -/// [`take_blocking`][]. +/// [`take_blocking`][]. If you want to access the manager instance even if the cache is poisoned, +/// use [`force_take`][]. /// /// # Errors /// @@ -385,6 +386,7 @@ pub fn take_blocking() -> Result, Error> { /// - [`PoisonError`][] if the lock is poisoned /// /// [`take_blocking`]: fn.take_blocking.html +/// [`force_take`]: fn.force_take.html /// [`ConcurrentAccessError`]: struct.Error.html#variant.ConcurrentAccessError /// [`PoisonError`]: struct.Error.html#variant.PoisonError /// [`Manager`]: struct.Manager.html @@ -392,6 +394,34 @@ pub fn take() -> Result, Error> { MANAGER.try_lock().map_err(Into::into) } +/// Try to take an instance of the connection manager, ignoring a poisoned cache. +/// +/// There may only be one [`Manager`][] instance at the same time. If there already is an +/// instance, a [`ConcurrentAccessError`][] is returned. If you want a blocking version, use +/// [`take_blocking`][]. +/// +/// If a thread has previously panicked while accessing the manager instance, the cache is +/// poisoned. The default implementation, [`take`][], returns a [`PoisonError`][] on subsequent +/// calls. This implementation ignores the poisoned cache and returns the manager instance. +/// +/// # Errors +/// +/// - [`ConcurrentAccessError`][] if the token for the `Manager` instance cannot be locked +/// +/// [`take`]: fn.take.html +/// [`take_blocking`]: fn.take_blocking.html +/// [`ConcurrentAccessError`]: struct.Error.html#variant.ConcurrentAccessError +/// [`Manager`]: struct.Manager.html +pub fn force_take() -> Result, Error> { + match take() { + Ok(guard) => Ok(guard), + Err(err) => match err { + Error::PoisonError(err) => Ok(err.into_inner()), + err => Err(err), + }, + } +} + /// Enables or disables debug output. Calling this method with `true` is equivalent to setting the /// log level to `Debug`; calling it with `false` is equivalent to the log level `Error` (see /// [`set_log_level`][]). -- cgit v1.2.3 From 6c138eaa850c745b97b7e48a201db0cbaad8e1e0 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 16 Jul 2019 08:58:37 +0000 Subject: Update rand_{core,os} dependencies This patch updates the rand_core dependency to version 0.5 and the rand_os dependency to version 0.2. This causes a change in util.rs: Instead of constructing an OsRng instance using OsRng::new(), we can directly instantiate the (now empty) struct. --- CHANGELOG.md | 9 ++++++--- Cargo.toml | 8 ++++---- src/util.rs | 3 +-- 3 files changed, 11 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 3051d0f..41e46a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,9 +38,12 @@ SPDX-License-Identifier: MIT - Implement `DerefMut` for `User` and `Admin`. - Add `device_mut` method to `DeviceWrapper`. - Require a mutable `Device` reference if a method changes the device state. -- Update the `nitrokey-sys` dependency to version 3.5.0. -- Update the `nitrokey-test` dependency to version 0.3 and add the - `nitrokey-test-state` dependency in version 0.1.0. +- Update dependencies: + - `nitrokey-sys` to 3.5 + - `nitrokey-test` to 0.3 + - `rand_core` to 0.5 + - `rand_os` to 0.2 +- Add `nitrokey-test-state` dependency in version 0.1. - Refactor connection management: - Add `ConcurrentAccessError` and `PoisonError` `Error` variants. - Add the `Manager` struct that manages connections to Nitrokey devices. diff --git a/Cargo.toml b/Cargo.toml index 62eea02..a912859 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,12 +17,12 @@ license = "MIT" exclude = [".builds/*"] [dependencies] -lazy_static = "1.2.0" +lazy_static = "1.2" libc = "0.2" nitrokey-sys = "3.5" -rand_core = {version = "0.3", default-features = false, features = ["std"] } -rand_os = {version = "0.1"} +rand_core = {version = "0.5", default-features = false, features = ["std"] } +rand_os = {version = "0.2"} [dev-dependencies] nitrokey-test = "0.3" -nitrokey-test-state = "0.1.0" +nitrokey-test-state = "0.1" diff --git a/src/util.rs b/src/util.rs index fdb73c3..a5dd1e5 100644 --- a/src/util.rs +++ b/src/util.rs @@ -77,9 +77,8 @@ pub fn get_last_error() -> Error { } pub fn generate_password(length: usize) -> Result, Error> { - let mut rng = OsRng::new().map_err(|err| Error::RandError(Box::new(err)))?; let mut data = vec![0u8; length]; - rng.fill_bytes(&mut data[..]); + OsRng.fill_bytes(&mut data[..]); Ok(data) } -- cgit v1.2.3 From 678f0b700666a4ba86db2180078d79a730dc82e0 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 16 Jul 2019 09:14:36 +0000 Subject: Refactor the device module into submodules This patch splits the rather large device module into the submodules pro, storage and wrapper. This only changes the internal code structure and does not affect the public API. --- CHANGELOG.md | 1 + src/device.rs | 1380 ------------------------------------------------- src/device/mod.rs | 466 +++++++++++++++++ src/device/pro.rs | 79 +++ src/device/storage.rs | 735 ++++++++++++++++++++++++++ src/device/wrapper.rs | 134 +++++ 6 files changed, 1415 insertions(+), 1380 deletions(-) delete mode 100644 src/device.rs create mode 100644 src/device/mod.rs create mode 100644 src/device/pro.rs create mode 100644 src/device/storage.rs create mode 100644 src/device/wrapper.rs (limited to 'src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 41e46a6..a5d049c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ SPDX-License-Identifier: MIT - Add the `into_manager` function to the `Device` trait. - Add the `force_take` function that ignores a `PoisonError` when accessing the manager instance. +- Internally refactor the `device` module into submodules. # v0.3.4 (2019-01-20) - Fix authentication methods that assumed that `char` is signed. diff --git a/src/device.rs b/src/device.rs deleted file mode 100644 index 758d4c1..0000000 --- a/src/device.rs +++ /dev/null @@ -1,1380 +0,0 @@ -// Copyright (C) 2018-2019 Robin Krahl -// SPDX-License-Identifier: MIT - -use std::fmt; - -use libc; -use nitrokey_sys; - -use crate::auth::Authenticate; -use crate::config::{Config, RawConfig}; -use crate::error::{CommunicationError, Error}; -use crate::otp::GenerateOtp; -use crate::pws::GetPasswordSafe; -use crate::util::{ - get_command_result, get_cstring, get_last_error, result_from_string, result_or_error, -}; - -/// Available Nitrokey models. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Model { - /// The Nitrokey Storage. - Storage, - /// The Nitrokey Pro. - Pro, -} - -impl fmt::Display for Model { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - Model::Pro => "Pro", - Model::Storage => "Storage", - }) - } -} - -/// The access mode of a volume on the Nitrokey Storage. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum VolumeMode { - /// A read-only volume. - ReadOnly, - /// A read-write volume. - ReadWrite, -} - -impl fmt::Display for VolumeMode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - VolumeMode::ReadOnly => "read-only", - VolumeMode::ReadWrite => "read-write", - }) - } -} - -/// A wrapper for a Nitrokey device of unknown type. -/// -/// Use the [`connect`][] method to obtain a wrapped instance. The wrapper implements all traits -/// that are shared between all Nitrokey devices so that the shared functionality can be used -/// without knowing the type of the underlying device. If you want to use functionality that is -/// not available for all devices, you have to extract the device. -/// -/// # Examples -/// -/// Authentication with error handling: -/// -/// ```no_run -/// use nitrokey::{Authenticate, DeviceWrapper, User}; -/// # use nitrokey::Error; -/// -/// fn perform_user_task<'a>(device: &User<'a, DeviceWrapper<'a>>) {} -/// fn perform_other_task(device: &DeviceWrapper) {} -/// -/// # fn try_main() -> Result<(), Error> { -/// let mut manager = nitrokey::take()?; -/// let device = manager.connect()?; -/// let device = match device.authenticate_user("123456") { -/// Ok(user) => { -/// perform_user_task(&user); -/// user.device() -/// }, -/// Err((device, err)) => { -/// eprintln!("Could not authenticate as user: {}", err); -/// device -/// }, -/// }; -/// perform_other_task(&device); -/// # Ok(()) -/// # } -/// ``` -/// -/// Device-specific commands: -/// -/// ```no_run -/// use nitrokey::{DeviceWrapper, Storage}; -/// # use nitrokey::Error; -/// -/// fn perform_common_task(device: &DeviceWrapper) {} -/// fn perform_storage_task(device: &Storage) {} -/// -/// # fn try_main() -> Result<(), Error> { -/// let mut manager = nitrokey::take()?; -/// let device = manager.connect()?; -/// perform_common_task(&device); -/// match device { -/// DeviceWrapper::Storage(storage) => perform_storage_task(&storage), -/// _ => (), -/// }; -/// # Ok(()) -/// # } -/// ``` -/// -/// [`connect`]: struct.Manager.html#method.connect -#[derive(Debug)] -pub enum DeviceWrapper<'a> { - /// A Nitrokey Storage device. - Storage(Storage<'a>), - /// A Nitrokey Pro device. - Pro(Pro<'a>), -} - -/// A Nitrokey Pro device without user or admin authentication. -/// -/// Use the [`connect`][] method to obtain an instance wrapper or the [`connect_pro`] method to -/// directly obtain an instance. If you want to execute a command that requires user or admin -/// authentication, use [`authenticate_admin`][] or [`authenticate_user`][]. -/// -/// # Examples -/// -/// Authentication with error handling: -/// -/// ```no_run -/// use nitrokey::{Authenticate, User, Pro}; -/// # use nitrokey::Error; -/// -/// fn perform_user_task<'a>(device: &User<'a, Pro<'a>>) {} -/// fn perform_other_task(device: &Pro) {} -/// -/// # fn try_main() -> Result<(), Error> { -/// let mut manager = nitrokey::take()?; -/// let device = manager.connect_pro()?; -/// let device = match device.authenticate_user("123456") { -/// Ok(user) => { -/// perform_user_task(&user); -/// user.device() -/// }, -/// Err((device, err)) => { -/// eprintln!("Could not authenticate as user: {}", err); -/// device -/// }, -/// }; -/// perform_other_task(&device); -/// # Ok(()) -/// # } -/// ``` -/// -/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin -/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user -/// [`connect`]: struct.Manager.html#method.connect -/// [`connect_pro`]: struct.Manager.html#method.connect_pro -#[derive(Debug)] -pub struct Pro<'a> { - manager: Option<&'a mut crate::Manager>, -} - -/// A Nitrokey Storage device without user or admin authentication. -/// -/// Use the [`connect`][] method to obtain an instance wrapper or the [`connect_storage`] method to -/// directly obtain an instance. If you want to execute a command that requires user or admin -/// authentication, use [`authenticate_admin`][] or [`authenticate_user`][]. -/// -/// # Examples -/// -/// Authentication with error handling: -/// -/// ```no_run -/// use nitrokey::{Authenticate, User, Storage}; -/// # use nitrokey::Error; -/// -/// fn perform_user_task<'a>(device: &User<'a, Storage<'a>>) {} -/// fn perform_other_task(device: &Storage) {} -/// -/// # fn try_main() -> Result<(), Error> { -/// let mut manager = nitrokey::take()?; -/// let device = manager.connect_storage()?; -/// let device = match device.authenticate_user("123456") { -/// Ok(user) => { -/// perform_user_task(&user); -/// user.device() -/// }, -/// Err((device, err)) => { -/// eprintln!("Could not authenticate as user: {}", err); -/// device -/// }, -/// }; -/// perform_other_task(&device); -/// # Ok(()) -/// # } -/// ``` -/// -/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin -/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user -/// [`connect`]: struct.Manager.html#method.connect -/// [`connect_storage`]: struct.Manager.html#method.connect_storage -#[derive(Debug)] -pub struct Storage<'a> { - manager: Option<&'a mut crate::Manager>, -} - -/// The status of a volume on a Nitrokey Storage device. -#[derive(Debug)] -pub struct VolumeStatus { - /// Indicates whether the volume is read-only. - pub read_only: bool, - /// Indicates whether the volume is active. - pub active: bool, -} - -/// Information about the SD card in a Storage device. -#[derive(Debug)] -pub struct SdCardData { - /// The serial number of the SD card. - pub serial_number: u32, - /// The size of the SD card in GB. - pub size: u8, - /// The year the card was manufactured, e. g. 17 for 2017. - pub manufacturing_year: u8, - /// The month the card was manufactured. - pub manufacturing_month: u8, - /// The OEM ID. - pub oem: u16, - /// The manufacturer ID. - pub manufacturer: u8, -} - -/// A firmware version for a Nitrokey device. -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct FirmwareVersion { - /// The major firmware version, e. g. 0 in v0.40. - pub major: u8, - /// The minor firmware version, e. g. 40 in v0.40. - pub minor: u8, -} - -impl fmt::Display for FirmwareVersion { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "v{}.{}", self.major, self.minor) - } -} - -/// Production information for a Storage device. -#[derive(Debug)] -pub struct StorageProductionInfo { - /// The firmware version. - pub firmware_version: FirmwareVersion, - /// The internal firmware version. - pub firmware_version_internal: u8, - /// The serial number of the CPU. - pub serial_number_cpu: u32, - /// Information about the SD card. - pub sd_card: SdCardData, -} - -/// The status of a Nitrokey Storage device. -#[derive(Debug)] -pub struct StorageStatus { - /// The status of the unencrypted volume. - pub unencrypted_volume: VolumeStatus, - /// The status of the encrypted volume. - pub encrypted_volume: VolumeStatus, - /// The status of the hidden volume. - pub hidden_volume: VolumeStatus, - /// The firmware version. - pub firmware_version: FirmwareVersion, - /// Indicates whether the firmware is locked. - pub firmware_locked: bool, - /// The serial number of the SD card in the Storage stick. - pub serial_number_sd_card: u32, - /// The serial number of the smart card in the Storage stick. - pub serial_number_smart_card: u32, - /// The number of remaining login attempts for the user PIN. - pub user_retry_count: u8, - /// The number of remaining login attempts for the admin PIN. - pub admin_retry_count: u8, - /// Indicates whether a new SD card was found. - pub new_sd_card_found: bool, - /// Indicates whether the SD card is filled with random characters. - pub filled_with_random: bool, - /// Indicates whether the stick has been initialized by generating - /// the AES keys. - pub stick_initialized: bool, -} - -/// 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 { - /// Returns the [`Manager`][] instance that has been used to connect to this device. - /// - /// # Example - /// - /// ``` - /// use nitrokey::{Device, DeviceWrapper}; - /// - /// fn do_something(device: DeviceWrapper) { - /// // reconnect to any device - /// let manager = device.into_manager(); - /// let device = manager.connect(); - /// // do something with the device - /// // ... - /// } - /// - /// # fn main() -> Result<(), nitrokey::Error> { - /// match nitrokey::take()?.connect() { - /// Ok(device) => do_something(device), - /// Err(err) => println!("Could not connect to a Nitrokey: {}", err), - /// } - /// # Ok(()) - /// # } - /// ``` - fn into_manager(self) -> &'a mut crate::Manager; - - /// Returns the model of the connected Nitrokey device. - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let device = manager.connect()?; - /// println!("Connected to a Nitrokey {}", device.get_model()); - /// # Ok(()) - /// # } - fn get_model(&self) -> Model; - - /// Returns the serial number of the Nitrokey device. The serial number is the string - /// representation of a hex number. - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let device = manager.connect()?; - /// match device.get_serial_number() { - /// Ok(number) => println!("serial no: {}", number), - /// Err(err) => eprintln!("Could not get serial number: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - fn get_serial_number(&self) -> Result { - result_from_string(unsafe { nitrokey_sys::NK_device_serial_number() }) - } - - /// Returns the number of remaining authentication attempts for the user. The total number of - /// available attempts is three. - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let device = manager.connect()?; - /// let count = device.get_user_retry_count(); - /// match device.get_user_retry_count() { - /// Ok(count) => println!("{} remaining authentication attempts (user)", count), - /// Err(err) => eprintln!("Could not get user retry count: {}", err), - /// } - /// # Ok(()) - /// # } - /// ``` - fn get_user_retry_count(&self) -> Result { - result_or_error(unsafe { nitrokey_sys::NK_get_user_retry_count() }) - } - - /// Returns the number of remaining authentication attempts for the admin. The total number of - /// available attempts is three. - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let device = manager.connect()?; - /// let count = device.get_admin_retry_count(); - /// match device.get_admin_retry_count() { - /// Ok(count) => println!("{} remaining authentication attempts (admin)", count), - /// Err(err) => eprintln!("Could not get admin retry count: {}", err), - /// } - /// # Ok(()) - /// # } - /// ``` - fn get_admin_retry_count(&self) -> Result { - result_or_error(unsafe { nitrokey_sys::NK_get_admin_retry_count() }) - } - - /// Returns the firmware version. - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let device = manager.connect()?; - /// match device.get_firmware_version() { - /// Ok(version) => println!("Firmware version: {}", version), - /// Err(err) => eprintln!("Could not access firmware version: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - fn get_firmware_version(&self) -> Result { - 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 }) - } - - /// Returns the current configuration of the Nitrokey device. - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// 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!("require password for OTP: {:?}", config.user_password); - /// # Ok(()) - /// # } - /// ``` - fn get_config(&self) -> Result { - let config_ptr = unsafe { nitrokey_sys::NK_read_config() }; - if config_ptr.is_null() { - return Err(get_last_error()); - } - let config_array_ptr = config_ptr as *const [u8; 5]; - let raw_config = unsafe { RawConfig::from(*config_array_ptr) }; - unsafe { libc::free(config_ptr as *mut libc::c_void) }; - Ok(raw_config.into()) - } - - /// Changes the administrator PIN. - /// - /// # Errors - /// - /// - [`InvalidString`][] if one of the provided passwords contains a null byte - /// - [`WrongPassword`][] if the current admin password is wrong - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let mut device = manager.connect()?; - /// match device.change_admin_pin("12345678", "12345679") { - /// Ok(()) => println!("Updated admin PIN."), - /// Err(err) => eprintln!("Failed to update admin PIN: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - /// - /// [`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()) - }) - } - - /// Changes the user PIN. - /// - /// # Errors - /// - /// - [`InvalidString`][] if one of the provided passwords contains a null byte - /// - [`WrongPassword`][] if the current user password is wrong - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let mut device = manager.connect()?; - /// match device.change_user_pin("123456", "123457") { - /// Ok(()) => println!("Updated admin PIN."), - /// Err(err) => eprintln!("Failed to update admin PIN: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - /// - /// [`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()) - }) - } - - /// Unlocks the user PIN after three failed login attempts and sets it to the given value. - /// - /// # Errors - /// - /// - [`InvalidString`][] if one of the provided passwords contains a null byte - /// - [`WrongPassword`][] if the admin password is wrong - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let mut device = manager.connect()?; - /// match device.unlock_user_pin("12345678", "123456") { - /// Ok(()) => println!("Unlocked user PIN."), - /// Err(err) => eprintln!("Failed to unlock user PIN: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - /// - /// [`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(), - ) - }) - } - - /// Locks the Nitrokey device. - /// - /// This disables the password store if it has been unlocked. On the Nitrokey Storage, this - /// also disables the volumes if they have been enabled. - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let mut device = manager.connect()?; - /// match device.lock() { - /// Ok(()) => println!("Locked the Nitrokey device."), - /// Err(err) => eprintln!("Could not lock the Nitrokey device: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - fn lock(&mut self) -> Result<(), Error> { - get_command_result(unsafe { nitrokey_sys::NK_lock_device() }) - } - - /// Performs a factory reset on the Nitrokey device. - /// - /// This commands performs a factory reset on the smart card (like the factory reset via `gpg - /// --card-edit`) and then clears the flash memory (password safe, one-time passwords etc.). - /// After a factory reset, [`build_aes_key`][] has to be called before the password safe or the - /// encrypted volume can be used. - /// - /// # Errors - /// - /// - [`InvalidString`][] if the provided password contains a null byte - /// - [`WrongPassword`][] if the admin password is wrong - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let mut device = manager.connect()?; - /// match device.factory_reset("12345678") { - /// Ok(()) => println!("Performed a factory reset."), - /// Err(err) => eprintln!("Could not perform a factory reset: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - /// - /// [`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()) }) - } - - /// Builds a new AES key on the Nitrokey. - /// - /// The AES key is used to encrypt the password safe and the encrypted volume. You may need - /// to call this method after a factory reset, either using [`factory_reset`][] or using `gpg - /// --card-edit`. You can also use it to destroy the data stored in the password safe or on - /// the encrypted volume. - /// - /// # Errors - /// - /// - [`InvalidString`][] if the provided password contains a null byte - /// - [`WrongPassword`][] if the admin password is wrong - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::Device; - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let mut device = manager.connect()?; - /// match device.build_aes_key("12345678") { - /// Ok(()) => println!("New AES keys have been built."), - /// Err(err) => eprintln!("Could not build new AES keys: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - /// - /// [`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()) }) - } -} - -fn get_connected_model() -> Option { - match unsafe { nitrokey_sys::NK_get_device_model() } { - nitrokey_sys::NK_device_model_NK_PRO => Some(Model::Pro), - nitrokey_sys::NK_device_model_NK_STORAGE => Some(Model::Storage), - _ => None, - } -} - -pub(crate) fn create_device_wrapper( - manager: &mut crate::Manager, - model: Model, -) -> DeviceWrapper<'_> { - match model { - Model::Pro => Pro::new(manager).into(), - Model::Storage => Storage::new(manager).into(), - } -} - -pub(crate) fn get_connected_device( - manager: &mut crate::Manager, -) -> Result, Error> { - match get_connected_model() { - Some(model) => Ok(create_device_wrapper(manager, model)), - None => Err(CommunicationError::NotConnected.into()), - } -} - -pub(crate) fn connect_enum(model: Model) -> bool { - let model = match model { - Model::Storage => nitrokey_sys::NK_device_model_NK_STORAGE, - Model::Pro => nitrokey_sys::NK_device_model_NK_PRO, - }; - unsafe { nitrokey_sys::NK_login_enum(model) == 1 } -} - -impl<'a> DeviceWrapper<'a> { - fn device(&self) -> &dyn Device<'a> { - match *self { - DeviceWrapper::Storage(ref storage) => storage, - DeviceWrapper::Pro(ref pro) => pro, - } - } - - fn device_mut(&mut self) -> &mut dyn Device<'a> { - match *self { - DeviceWrapper::Storage(ref mut storage) => storage, - DeviceWrapper::Pro(ref mut pro) => pro, - } - } -} - -impl<'a> From> for DeviceWrapper<'a> { - fn from(device: Pro<'a>) -> Self { - DeviceWrapper::Pro(device) - } -} - -impl<'a> From> for DeviceWrapper<'a> { - fn from(device: Storage<'a>) -> Self { - DeviceWrapper::Storage(device) - } -} - -impl<'a> GenerateOtp for DeviceWrapper<'a> { - fn get_hotp_slot_name(&self, slot: u8) -> Result { - self.device().get_hotp_slot_name(slot) - } - - fn get_totp_slot_name(&self, slot: u8) -> Result { - self.device().get_totp_slot_name(slot) - } - - fn get_hotp_code(&mut self, slot: u8) -> Result { - self.device_mut().get_hotp_code(slot) - } - - fn get_totp_code(&self, slot: u8) -> Result { - self.device().get_totp_code(slot) - } -} - -impl<'a> Device<'a> for DeviceWrapper<'a> { - fn into_manager(self) -> &'a mut crate::Manager { - match self { - DeviceWrapper::Pro(dev) => dev.into_manager(), - DeviceWrapper::Storage(dev) => dev.into_manager(), - } - } - - fn get_model(&self) -> Model { - match *self { - DeviceWrapper::Pro(_) => Model::Pro, - DeviceWrapper::Storage(_) => Model::Storage, - } - } -} - -impl<'a> Pro<'a> { - pub(crate) fn new(manager: &'a mut crate::Manager) -> Pro<'a> { - Pro { - manager: Some(manager), - } - } -} - -impl<'a> Drop for Pro<'a> { - fn drop(&mut self) { - unsafe { - nitrokey_sys::NK_logout(); - } - } -} - -impl<'a> Device<'a> for Pro<'a> { - fn into_manager(mut self) -> &'a mut crate::Manager { - self.manager.take().unwrap() - } - - fn get_model(&self) -> Model { - Model::Pro - } -} - -impl<'a> GenerateOtp for Pro<'a> {} - -impl<'a> Storage<'a> { - pub(crate) fn new(manager: &'a mut crate::Manager) -> Storage<'a> { - Storage { - manager: Some(manager), - } - } - - /// Changes the update PIN. - /// - /// The update PIN is used to enable firmware updates. Unlike the user and the admin PIN, the - /// update PIN is not managed by the OpenPGP smart card but by the Nitrokey firmware. There is - /// no retry counter as with the other PIN types. - /// - /// # Errors - /// - /// - [`InvalidString`][] if one of the provided passwords contains a null byte - /// - [`WrongPassword`][] if the current update password is wrong - /// - /// # Example - /// - /// ```no_run - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let mut device = manager.connect_storage()?; - /// match device.change_update_pin("12345678", "87654321") { - /// Ok(()) => println!("Updated update PIN."), - /// Err(err) => eprintln!("Failed to update update PIN: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - /// - /// [`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()) - }) - } - - /// Enables the firmware update mode. - /// - /// During firmware update mode, the Nitrokey can no longer be accessed using HID commands. - /// To resume normal operation, run `dfu-programmer at32uc3a3256s launch`. In order to enter - /// the firmware update mode, you need the update password that can be changed using the - /// [`change_update_pin`][] method. - /// - /// # Errors - /// - /// - [`InvalidString`][] if one of the provided passwords contains a null byte - /// - [`WrongPassword`][] if the current update password is wrong - /// - /// # Example - /// - /// ```no_run - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let mut device = manager.connect_storage()?; - /// match device.enable_firmware_update("12345678") { - /// Ok(()) => println!("Nitrokey entered update mode."), - /// Err(err) => eprintln!("Could not enter update mode: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - /// - /// [`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()) - }) - } - - /// Enables the encrypted storage volume. - /// - /// Once the encrypted volume is enabled, it is presented to the operating system as a block - /// device. The API does not provide any information on the name or path of this block device. - /// - /// # Errors - /// - /// - [`InvalidString`][] if the provided password contains a null byte - /// - [`WrongPassword`][] if the provided user password is wrong - /// - /// # Example - /// - /// ```no_run - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let mut device = manager.connect_storage()?; - /// match device.enable_encrypted_volume("123456") { - /// Ok(()) => println!("Enabled the encrypted volume."), - /// Err(err) => eprintln!("Could not enable the encrypted volume: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - /// - /// [`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()) }) - } - - /// Disables the encrypted storage volume. - /// - /// Once the volume is disabled, it can be no longer accessed as a block device. If the - /// encrypted volume has not been enabled, this method still returns a success. - /// - /// # Example - /// - /// ```no_run - /// # use nitrokey::Error; - /// - /// fn use_volume() {} - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let mut device = manager.connect_storage()?; - /// match device.enable_encrypted_volume("123456") { - /// Ok(()) => { - /// println!("Enabled the encrypted volume."); - /// use_volume(); - /// match device.disable_encrypted_volume() { - /// Ok(()) => println!("Disabled the encrypted volume."), - /// Err(err) => { - /// eprintln!("Could not disable the encrypted volume: {}", err); - /// }, - /// }; - /// }, - /// Err(err) => eprintln!("Could not enable the encrypted volume: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - pub fn disable_encrypted_volume(&mut self) -> Result<(), Error> { - get_command_result(unsafe { nitrokey_sys::NK_lock_encrypted_volume() }) - } - - /// Enables a hidden storage volume. - /// - /// This function will only succeed if the encrypted storage ([`enable_encrypted_volume`][]) or - /// another hidden volume has been enabled previously. Once the hidden volume is enabled, it - /// is presented to the operating system as a block device and any previously opened encrypted - /// or hidden volumes are closed. The API does not provide any information on the name or path - /// of this block device. - /// - /// Note that the encrypted and the hidden volumes operate on the same storage area, so using - /// both at the same time might lead to data loss. - /// - /// The hidden volume to unlock is selected based on the provided password. - /// - /// # Errors - /// - /// - [`AesDecryptionFailed`][] if the encrypted storage has not been opened before calling - /// this method or the AES key has not been built - /// - [`InvalidString`][] if the provided password contains a null byte - /// - /// # Example - /// - /// ```no_run - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let mut device = manager.connect_storage()?; - /// device.enable_encrypted_volume("123445")?; - /// match device.enable_hidden_volume("hidden-pw") { - /// Ok(()) => println!("Enabled a hidden volume."), - /// Err(err) => eprintln!("Could not enable the hidden volume: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - /// - /// [`enable_encrypted_volume`]: #method.enable_encrypted_volume - /// [`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()) - }) - } - - /// Disables a hidden storage volume. - /// - /// Once the volume is disabled, it can be no longer accessed as a block device. If no hidden - /// volume has been enabled, this method still returns a success. - /// - /// # Example - /// - /// ```no_run - /// # use nitrokey::Error; - /// - /// fn use_volume() {} - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let mut device = manager.connect_storage()?; - /// device.enable_encrypted_volume("123445")?; - /// match device.enable_hidden_volume("hidden-pw") { - /// Ok(()) => { - /// println!("Enabled the hidden volume."); - /// use_volume(); - /// match device.disable_hidden_volume() { - /// Ok(()) => println!("Disabled the hidden volume."), - /// Err(err) => { - /// eprintln!("Could not disable the hidden volume: {}", err); - /// }, - /// }; - /// }, - /// Err(err) => eprintln!("Could not enable the hidden volume: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - pub fn disable_hidden_volume(&mut self) -> Result<(), Error> { - get_command_result(unsafe { nitrokey_sys::NK_lock_hidden_volume() }) - } - - /// Creates a hidden volume. - /// - /// The volume is crated in the given slot and in the given range of the available memory, - /// where `start` is the start position as a percentage of the available memory, and `end` is - /// the end position as a percentage of the available memory. The volume will be protected by - /// the given password. - /// - /// Note that the encrypted and the hidden volumes operate on the same storage area, so using - /// both at the same time might lead to data loss. - /// - /// According to the libnitrokey documentation, this function only works if the encrypted - /// storage has been opened. - /// - /// # Errors - /// - /// - [`AesDecryptionFailed`][] if the encrypted storage has not been opened before calling - /// this method or the AES key has not been built - /// - [`InvalidString`][] if the provided password contains a null byte - /// - /// # Example - /// - /// ```no_run - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let mut device = manager.connect_storage()?; - /// device.enable_encrypted_volume("123445")?; - /// device.create_hidden_volume(0, 0, 100, "hidden-pw")?; - /// # Ok(()) - /// # } - /// ``` - /// - /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed - /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString - pub fn create_hidden_volume( - &mut self, - slot: u8, - start: u8, - 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()) - }) - } - - /// Sets the access mode of the unencrypted volume. - /// - /// This command will reconnect the unencrypted volume so buffers should be flushed before - /// calling it. Since firmware version v0.51, this command requires the admin PIN. Older - /// firmware versions are not supported. - /// - /// # Errors - /// - /// - [`InvalidString`][] if the provided password contains a null byte - /// - [`WrongPassword`][] if the provided admin password is wrong - /// - /// # Example - /// - /// ```no_run - /// # use nitrokey::Error; - /// use nitrokey::VolumeMode; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let mut device = manager.connect_storage()?; - /// match device.set_unencrypted_volume_mode("12345678", VolumeMode::ReadWrite) { - /// Ok(()) => println!("Set the unencrypted volume to read-write mode."), - /// Err(err) => eprintln!("Could not set the unencrypted volume to read-write mode: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - /// - /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString - /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - pub fn set_unencrypted_volume_mode( - &mut self, - 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) - } - - /// Sets the access mode of the encrypted volume. - /// - /// This command will reconnect the encrypted volume so buffers should be flushed before - /// calling it. It is only available in firmware version 0.49. - /// - /// # Errors - /// - /// - [`InvalidString`][] if the provided password contains a null byte - /// - [`WrongPassword`][] if the provided admin password is wrong - /// - /// # Example - /// - /// ```no_run - /// # use nitrokey::Error; - /// use nitrokey::VolumeMode; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let mut device = manager.connect_storage()?; - /// match device.set_encrypted_volume_mode("12345678", VolumeMode::ReadWrite) { - /// Ok(()) => println!("Set the encrypted volume to read-write mode."), - /// Err(err) => eprintln!("Could not set the encrypted volume to read-write mode: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - /// - /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString - /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - pub fn set_encrypted_volume_mode( - &mut self, - 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) - } - - /// Returns the status of the connected storage device. - /// - /// # Example - /// - /// ```no_run - /// # use nitrokey::Error; - /// - /// fn use_volume() {} - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let device = manager.connect_storage()?; - /// match device.get_status() { - /// Ok(status) => { - /// println!("SD card ID: {:#x}", status.serial_number_sd_card); - /// }, - /// Err(err) => eprintln!("Could not get Storage status: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - pub fn get_status(&self) -> Result { - let mut raw_status = nitrokey_sys::NK_storage_status { - unencrypted_volume_read_only: false, - unencrypted_volume_active: false, - encrypted_volume_read_only: false, - encrypted_volume_active: false, - hidden_volume_read_only: false, - hidden_volume_active: false, - firmware_version_major: 0, - firmware_version_minor: 0, - firmware_locked: false, - serial_number_sd_card: 0, - serial_number_smart_card: 0, - user_retry_count: 0, - admin_retry_count: 0, - new_sd_card_found: false, - filled_with_random: false, - stick_initialized: false, - }; - let raw_result = unsafe { nitrokey_sys::NK_get_status_storage(&mut raw_status) }; - get_command_result(raw_result).map(|_| StorageStatus::from(raw_status)) - } - - /// Returns the production information for the connected storage device. - /// - /// # Example - /// - /// ```no_run - /// # use nitrokey::Error; - /// - /// fn use_volume() {} - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let device = manager.connect_storage()?; - /// match device.get_production_info() { - /// Ok(data) => { - /// println!("SD card ID: {:#x}", data.sd_card.serial_number); - /// println!("SD card size: {} GB", data.sd_card.size); - /// }, - /// Err(err) => eprintln!("Could not get Storage production info: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - pub fn get_production_info(&self) -> Result { - let mut raw_data = nitrokey_sys::NK_storage_ProductionTest { - FirmwareVersion_au8: [0, 2], - FirmwareVersionInternal_u8: 0, - SD_Card_Size_u8: 0, - CPU_CardID_u32: 0, - SmartCardID_u32: 0, - SD_CardID_u32: 0, - SC_UserPwRetryCount: 0, - SC_AdminPwRetryCount: 0, - SD_Card_ManufacturingYear_u8: 0, - SD_Card_ManufacturingMonth_u8: 0, - SD_Card_OEM_u16: 0, - SD_WriteSpeed_u16: 0, - SD_Card_Manufacturer_u8: 0, - }; - let raw_result = unsafe { nitrokey_sys::NK_get_storage_production_info(&mut raw_data) }; - get_command_result(raw_result).map(|_| StorageProductionInfo::from(raw_data)) - } - - /// Clears the warning for a new SD card. - /// - /// The Storage status contains a field for a new SD card warning. After a factory reset, the - /// field is set to true. After filling the SD card with random data, it is set to false. - /// This method can be used to set it to false without filling the SD card with random data. - /// - /// # Errors - /// - /// - [`InvalidString`][] if the provided password contains a null byte - /// - [`WrongPassword`][] if the provided admin password is wrong - /// - /// # Example - /// - /// ```no_run - /// # use nitrokey::Error; - /// - /// # fn try_main() -> Result<(), Error> { - /// let mut manager = nitrokey::take()?; - /// let mut device = manager.connect_storage()?; - /// match device.clear_new_sd_card_warning("12345678") { - /// Ok(()) => println!("Cleared the new SD card warning."), - /// Err(err) => eprintln!("Could not set the clear the new SD card warning: {}", err), - /// }; - /// # Ok(()) - /// # } - /// ``` - /// - /// [`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()) - }) - } - - /// 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() }) - } - - /// Exports the firmware to the unencrypted volume. - /// - /// This command requires the admin PIN. The unencrypted volume must be in read-write mode - /// when this command is executed. Otherwise, it will still return `Ok` but not write the - /// firmware. - /// - /// This command unmounts the unencrypted volume if it has been mounted, so all buffers should - /// be flushed. The firmware is written to the `firmware.bin` file on the unencrypted volume. - /// - /// # Errors - /// - /// - [`InvalidString`][] if one of the provided passwords contains a null byte - /// - [`WrongPassword`][] if the admin password is wrong - /// - /// [`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()) }) - } -} - -impl<'a> Drop for Storage<'a> { - fn drop(&mut self) { - unsafe { - nitrokey_sys::NK_logout(); - } - } -} - -impl<'a> Device<'a> for Storage<'a> { - fn into_manager(mut self) -> &'a mut crate::Manager { - self.manager.take().unwrap() - } - - fn get_model(&self) -> Model { - Model::Storage - } -} - -impl<'a> GenerateOtp for Storage<'a> {} - -impl From 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 From 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, - } - } -} diff --git a/src/device/mod.rs b/src/device/mod.rs new file mode 100644 index 0000000..af28ab5 --- /dev/null +++ b/src/device/mod.rs @@ -0,0 +1,466 @@ +// Copyright (C) 2018-2019 Robin Krahl +// SPDX-License-Identifier: MIT + +mod pro; +mod storage; +mod wrapper; + +use std::fmt; + +use libc; +use nitrokey_sys; + +use crate::auth::Authenticate; +use crate::config::{Config, RawConfig}; +use crate::error::{CommunicationError, Error}; +use crate::otp::GenerateOtp; +use crate::pws::GetPasswordSafe; +use crate::util::{ + get_command_result, get_cstring, get_last_error, result_from_string, result_or_error, +}; + +pub use pro::Pro; +pub use storage::{ + SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus, +}; +pub use wrapper::DeviceWrapper; + +/// Available Nitrokey models. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Model { + /// The Nitrokey Storage. + Storage, + /// The Nitrokey Pro. + Pro, +} + +impl fmt::Display for Model { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match *self { + Model::Pro => "Pro", + Model::Storage => "Storage", + }) + } +} + +/// A firmware version for a Nitrokey device. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct FirmwareVersion { + /// The major firmware version, e. g. 0 in v0.40. + pub major: u8, + /// The minor firmware version, e. g. 40 in v0.40. + pub minor: u8, +} + +impl fmt::Display for FirmwareVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "v{}.{}", self.major, self.minor) + } +} + +/// 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 { + /// Returns the [`Manager`][] instance that has been used to connect to this device. + /// + /// # Example + /// + /// ``` + /// use nitrokey::{Device, DeviceWrapper}; + /// + /// fn do_something(device: DeviceWrapper) { + /// // reconnect to any device + /// let manager = device.into_manager(); + /// let device = manager.connect(); + /// // do something with the device + /// // ... + /// } + /// + /// # fn main() -> Result<(), nitrokey::Error> { + /// match nitrokey::take()?.connect() { + /// Ok(device) => do_something(device), + /// Err(err) => println!("Could not connect to a Nitrokey: {}", err), + /// } + /// # Ok(()) + /// # } + /// ``` + fn into_manager(self) -> &'a mut crate::Manager; + + /// Returns the model of the connected Nitrokey device. + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::Error; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; + /// println!("Connected to a Nitrokey {}", device.get_model()); + /// # Ok(()) + /// # } + fn get_model(&self) -> Model; + + /// Returns the serial number of the Nitrokey device. The serial number is the string + /// representation of a hex number. + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::Error; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; + /// match device.get_serial_number() { + /// Ok(number) => println!("serial no: {}", number), + /// Err(err) => eprintln!("Could not get serial number: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + fn get_serial_number(&self) -> Result { + result_from_string(unsafe { nitrokey_sys::NK_device_serial_number() }) + } + + /// Returns the number of remaining authentication attempts for the user. The total number of + /// available attempts is three. + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::Error; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; + /// let count = device.get_user_retry_count(); + /// match device.get_user_retry_count() { + /// Ok(count) => println!("{} remaining authentication attempts (user)", count), + /// Err(err) => eprintln!("Could not get user retry count: {}", err), + /// } + /// # Ok(()) + /// # } + /// ``` + fn get_user_retry_count(&self) -> Result { + result_or_error(unsafe { nitrokey_sys::NK_get_user_retry_count() }) + } + + /// Returns the number of remaining authentication attempts for the admin. The total number of + /// available attempts is three. + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::Error; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; + /// let count = device.get_admin_retry_count(); + /// match device.get_admin_retry_count() { + /// Ok(count) => println!("{} remaining authentication attempts (admin)", count), + /// Err(err) => eprintln!("Could not get admin retry count: {}", err), + /// } + /// # Ok(()) + /// # } + /// ``` + fn get_admin_retry_count(&self) -> Result { + result_or_error(unsafe { nitrokey_sys::NK_get_admin_retry_count() }) + } + + /// Returns the firmware version. + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::Error; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; + /// match device.get_firmware_version() { + /// Ok(version) => println!("Firmware version: {}", version), + /// Err(err) => eprintln!("Could not access firmware version: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + fn get_firmware_version(&self) -> Result { + 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 }) + } + + /// Returns the current configuration of the Nitrokey device. + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::Error; + /// + /// # fn try_main() -> Result<(), Error> { + /// 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!("require password for OTP: {:?}", config.user_password); + /// # Ok(()) + /// # } + /// ``` + fn get_config(&self) -> Result { + let config_ptr = unsafe { nitrokey_sys::NK_read_config() }; + if config_ptr.is_null() { + return Err(get_last_error()); + } + let config_array_ptr = config_ptr as *const [u8; 5]; + let raw_config = unsafe { RawConfig::from(*config_array_ptr) }; + unsafe { libc::free(config_ptr as *mut libc::c_void) }; + Ok(raw_config.into()) + } + + /// Changes the administrator PIN. + /// + /// # Errors + /// + /// - [`InvalidString`][] if one of the provided passwords contains a null byte + /// - [`WrongPassword`][] if the current admin password is wrong + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::Error; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; + /// match device.change_admin_pin("12345678", "12345679") { + /// Ok(()) => println!("Updated admin PIN."), + /// Err(err) => eprintln!("Failed to update admin PIN: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`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()) + }) + } + + /// Changes the user PIN. + /// + /// # Errors + /// + /// - [`InvalidString`][] if one of the provided passwords contains a null byte + /// - [`WrongPassword`][] if the current user password is wrong + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::Error; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; + /// match device.change_user_pin("123456", "123457") { + /// Ok(()) => println!("Updated admin PIN."), + /// Err(err) => eprintln!("Failed to update admin PIN: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`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()) + }) + } + + /// Unlocks the user PIN after three failed login attempts and sets it to the given value. + /// + /// # Errors + /// + /// - [`InvalidString`][] if one of the provided passwords contains a null byte + /// - [`WrongPassword`][] if the admin password is wrong + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::Error; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; + /// match device.unlock_user_pin("12345678", "123456") { + /// Ok(()) => println!("Unlocked user PIN."), + /// Err(err) => eprintln!("Failed to unlock user PIN: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`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(), + ) + }) + } + + /// Locks the Nitrokey device. + /// + /// This disables the password store if it has been unlocked. On the Nitrokey Storage, this + /// also disables the volumes if they have been enabled. + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::Error; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; + /// match device.lock() { + /// Ok(()) => println!("Locked the Nitrokey device."), + /// Err(err) => eprintln!("Could not lock the Nitrokey device: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + fn lock(&mut self) -> Result<(), Error> { + get_command_result(unsafe { nitrokey_sys::NK_lock_device() }) + } + + /// Performs a factory reset on the Nitrokey device. + /// + /// This commands performs a factory reset on the smart card (like the factory reset via `gpg + /// --card-edit`) and then clears the flash memory (password safe, one-time passwords etc.). + /// After a factory reset, [`build_aes_key`][] has to be called before the password safe or the + /// encrypted volume can be used. + /// + /// # Errors + /// + /// - [`InvalidString`][] if the provided password contains a null byte + /// - [`WrongPassword`][] if the admin password is wrong + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::Error; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; + /// match device.factory_reset("12345678") { + /// Ok(()) => println!("Performed a factory reset."), + /// Err(err) => eprintln!("Could not perform a factory reset: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`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()) }) + } + + /// Builds a new AES key on the Nitrokey. + /// + /// The AES key is used to encrypt the password safe and the encrypted volume. You may need + /// to call this method after a factory reset, either using [`factory_reset`][] or using `gpg + /// --card-edit`. You can also use it to destroy the data stored in the password safe or on + /// the encrypted volume. + /// + /// # Errors + /// + /// - [`InvalidString`][] if the provided password contains a null byte + /// - [`WrongPassword`][] if the admin password is wrong + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::Error; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect()?; + /// match device.build_aes_key("12345678") { + /// Ok(()) => println!("New AES keys have been built."), + /// Err(err) => eprintln!("Could not build new AES keys: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`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()) }) + } +} + +fn get_connected_model() -> Option { + match unsafe { nitrokey_sys::NK_get_device_model() } { + nitrokey_sys::NK_device_model_NK_PRO => Some(Model::Pro), + nitrokey_sys::NK_device_model_NK_STORAGE => Some(Model::Storage), + _ => None, + } +} + +pub(crate) fn create_device_wrapper( + manager: &mut crate::Manager, + model: Model, +) -> DeviceWrapper<'_> { + match model { + Model::Pro => Pro::new(manager).into(), + Model::Storage => Storage::new(manager).into(), + } +} + +pub(crate) fn get_connected_device( + manager: &mut crate::Manager, +) -> Result, Error> { + match get_connected_model() { + Some(model) => Ok(create_device_wrapper(manager, model)), + None => Err(CommunicationError::NotConnected.into()), + } +} + +pub(crate) fn connect_enum(model: Model) -> bool { + let model = match model { + Model::Storage => nitrokey_sys::NK_device_model_NK_STORAGE, + Model::Pro => nitrokey_sys::NK_device_model_NK_PRO, + }; + unsafe { nitrokey_sys::NK_login_enum(model) == 1 } +} diff --git a/src/device/pro.rs b/src/device/pro.rs new file mode 100644 index 0000000..a65345e --- /dev/null +++ b/src/device/pro.rs @@ -0,0 +1,79 @@ +// Copyright (C) 2018-2019 Robin Krahl +// SPDX-License-Identifier: MIT + +use nitrokey_sys; + +use crate::device::{Device, Model}; +use crate::otp::GenerateOtp; + +/// A Nitrokey Pro device without user or admin authentication. +/// +/// Use the [`connect`][] method to obtain an instance wrapper or the [`connect_pro`] method to +/// directly obtain an instance. If you want to execute a command that requires user or admin +/// authentication, use [`authenticate_admin`][] or [`authenticate_user`][]. +/// +/// # Examples +/// +/// Authentication with error handling: +/// +/// ```no_run +/// use nitrokey::{Authenticate, User, Pro}; +/// # use nitrokey::Error; +/// +/// fn perform_user_task<'a>(device: &User<'a, Pro<'a>>) {} +/// fn perform_other_task(device: &Pro) {} +/// +/// # fn try_main() -> Result<(), Error> { +/// let mut manager = nitrokey::take()?; +/// let device = manager.connect_pro()?; +/// let device = match device.authenticate_user("123456") { +/// Ok(user) => { +/// perform_user_task(&user); +/// user.device() +/// }, +/// Err((device, err)) => { +/// eprintln!("Could not authenticate as user: {}", err); +/// device +/// }, +/// }; +/// perform_other_task(&device); +/// # Ok(()) +/// # } +/// ``` +/// +/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin +/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user +/// [`connect`]: struct.Manager.html#method.connect +/// [`connect_pro`]: struct.Manager.html#method.connect_pro +#[derive(Debug)] +pub struct Pro<'a> { + manager: Option<&'a mut crate::Manager>, +} + +impl<'a> Pro<'a> { + pub(crate) fn new(manager: &'a mut crate::Manager) -> Pro<'a> { + Pro { + manager: Some(manager), + } + } +} + +impl<'a> Drop for Pro<'a> { + fn drop(&mut self) { + unsafe { + nitrokey_sys::NK_logout(); + } + } +} + +impl<'a> Device<'a> for Pro<'a> { + fn into_manager(mut self) -> &'a mut crate::Manager { + self.manager.take().unwrap() + } + + fn get_model(&self) -> Model { + Model::Pro + } +} + +impl<'a> GenerateOtp for Pro<'a> {} diff --git a/src/device/storage.rs b/src/device/storage.rs new file mode 100644 index 0000000..370ce36 --- /dev/null +++ b/src/device/storage.rs @@ -0,0 +1,735 @@ +// Copyright (C) 2018-2019 Robin Krahl +// SPDX-License-Identifier: MIT + +use std::fmt; + +use nitrokey_sys; + +use crate::device::{Device, FirmwareVersion, Model}; +use crate::error::Error; +use crate::otp::GenerateOtp; +use crate::util::{get_command_result, get_cstring}; + +/// A Nitrokey Storage device without user or admin authentication. +/// +/// Use the [`connect`][] method to obtain an instance wrapper or the [`connect_storage`] method to +/// directly obtain an instance. If you want to execute a command that requires user or admin +/// authentication, use [`authenticate_admin`][] or [`authenticate_user`][]. +/// +/// # Examples +/// +/// Authentication with error handling: +/// +/// ```no_run +/// use nitrokey::{Authenticate, User, Storage}; +/// # use nitrokey::Error; +/// +/// fn perform_user_task<'a>(device: &User<'a, Storage<'a>>) {} +/// fn perform_other_task(device: &Storage) {} +/// +/// # fn try_main() -> Result<(), Error> { +/// let mut manager = nitrokey::take()?; +/// let device = manager.connect_storage()?; +/// let device = match device.authenticate_user("123456") { +/// Ok(user) => { +/// perform_user_task(&user); +/// user.device() +/// }, +/// Err((device, err)) => { +/// eprintln!("Could not authenticate as user: {}", err); +/// device +/// }, +/// }; +/// perform_other_task(&device); +/// # Ok(()) +/// # } +/// ``` +/// +/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin +/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user +/// [`connect`]: struct.Manager.html#method.connect +/// [`connect_storage`]: struct.Manager.html#method.connect_storage +#[derive(Debug)] +pub struct Storage<'a> { + manager: Option<&'a mut crate::Manager>, +} + +/// The access mode of a volume on the Nitrokey Storage. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum VolumeMode { + /// A read-only volume. + ReadOnly, + /// A read-write volume. + ReadWrite, +} + +impl fmt::Display for VolumeMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match *self { + VolumeMode::ReadOnly => "read-only", + VolumeMode::ReadWrite => "read-write", + }) + } +} + +/// The status of a volume on a Nitrokey Storage device. +#[derive(Debug)] +pub struct VolumeStatus { + /// Indicates whether the volume is read-only. + pub read_only: bool, + /// Indicates whether the volume is active. + pub active: bool, +} + +/// Information about the SD card in a Storage device. +#[derive(Debug)] +pub struct SdCardData { + /// The serial number of the SD card. + pub serial_number: u32, + /// The size of the SD card in GB. + pub size: u8, + /// The year the card was manufactured, e. g. 17 for 2017. + pub manufacturing_year: u8, + /// The month the card was manufactured. + pub manufacturing_month: u8, + /// The OEM ID. + pub oem: u16, + /// The manufacturer ID. + pub manufacturer: u8, +} + +/// Production information for a Storage device. +#[derive(Debug)] +pub struct StorageProductionInfo { + /// The firmware version. + pub firmware_version: FirmwareVersion, + /// The internal firmware version. + pub firmware_version_internal: u8, + /// The serial number of the CPU. + pub serial_number_cpu: u32, + /// Information about the SD card. + pub sd_card: SdCardData, +} + +/// The status of a Nitrokey Storage device. +#[derive(Debug)] +pub struct StorageStatus { + /// The status of the unencrypted volume. + pub unencrypted_volume: VolumeStatus, + /// The status of the encrypted volume. + pub encrypted_volume: VolumeStatus, + /// The status of the hidden volume. + pub hidden_volume: VolumeStatus, + /// The firmware version. + pub firmware_version: FirmwareVersion, + /// Indicates whether the firmware is locked. + pub firmware_locked: bool, + /// The serial number of the SD card in the Storage stick. + pub serial_number_sd_card: u32, + /// The serial number of the smart card in the Storage stick. + pub serial_number_smart_card: u32, + /// The number of remaining login attempts for the user PIN. + pub user_retry_count: u8, + /// The number of remaining login attempts for the admin PIN. + pub admin_retry_count: u8, + /// Indicates whether a new SD card was found. + pub new_sd_card_found: bool, + /// Indicates whether the SD card is filled with random characters. + pub filled_with_random: bool, + /// Indicates whether the stick has been initialized by generating + /// the AES keys. + pub stick_initialized: bool, +} + +impl<'a> Storage<'a> { + pub(crate) fn new(manager: &'a mut crate::Manager) -> Storage<'a> { + Storage { + manager: Some(manager), + } + } + + /// Changes the update PIN. + /// + /// The update PIN is used to enable firmware updates. Unlike the user and the admin PIN, the + /// update PIN is not managed by the OpenPGP smart card but by the Nitrokey firmware. There is + /// no retry counter as with the other PIN types. + /// + /// # Errors + /// + /// - [`InvalidString`][] if one of the provided passwords contains a null byte + /// - [`WrongPassword`][] if the current update password is wrong + /// + /// # Example + /// + /// ```no_run + /// # use nitrokey::Error; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; + /// match device.change_update_pin("12345678", "87654321") { + /// Ok(()) => println!("Updated update PIN."), + /// Err(err) => eprintln!("Failed to update update PIN: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`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()) + }) + } + + /// Enables the firmware update mode. + /// + /// During firmware update mode, the Nitrokey can no longer be accessed using HID commands. + /// To resume normal operation, run `dfu-programmer at32uc3a3256s launch`. In order to enter + /// the firmware update mode, you need the update password that can be changed using the + /// [`change_update_pin`][] method. + /// + /// # Errors + /// + /// - [`InvalidString`][] if one of the provided passwords contains a null byte + /// - [`WrongPassword`][] if the current update password is wrong + /// + /// # Example + /// + /// ```no_run + /// # use nitrokey::Error; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; + /// match device.enable_firmware_update("12345678") { + /// Ok(()) => println!("Nitrokey entered update mode."), + /// Err(err) => eprintln!("Could not enter update mode: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`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()) + }) + } + + /// Enables the encrypted storage volume. + /// + /// Once the encrypted volume is enabled, it is presented to the operating system as a block + /// device. The API does not provide any information on the name or path of this block device. + /// + /// # Errors + /// + /// - [`InvalidString`][] if the provided password contains a null byte + /// - [`WrongPassword`][] if the provided user password is wrong + /// + /// # Example + /// + /// ```no_run + /// # use nitrokey::Error; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; + /// match device.enable_encrypted_volume("123456") { + /// Ok(()) => println!("Enabled the encrypted volume."), + /// Err(err) => eprintln!("Could not enable the encrypted volume: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`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()) }) + } + + /// Disables the encrypted storage volume. + /// + /// Once the volume is disabled, it can be no longer accessed as a block device. If the + /// encrypted volume has not been enabled, this method still returns a success. + /// + /// # Example + /// + /// ```no_run + /// # use nitrokey::Error; + /// + /// fn use_volume() {} + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; + /// match device.enable_encrypted_volume("123456") { + /// Ok(()) => { + /// println!("Enabled the encrypted volume."); + /// use_volume(); + /// match device.disable_encrypted_volume() { + /// Ok(()) => println!("Disabled the encrypted volume."), + /// Err(err) => { + /// eprintln!("Could not disable the encrypted volume: {}", err); + /// }, + /// }; + /// }, + /// Err(err) => eprintln!("Could not enable the encrypted volume: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + pub fn disable_encrypted_volume(&mut self) -> Result<(), Error> { + get_command_result(unsafe { nitrokey_sys::NK_lock_encrypted_volume() }) + } + + /// Enables a hidden storage volume. + /// + /// This function will only succeed if the encrypted storage ([`enable_encrypted_volume`][]) or + /// another hidden volume has been enabled previously. Once the hidden volume is enabled, it + /// is presented to the operating system as a block device and any previously opened encrypted + /// or hidden volumes are closed. The API does not provide any information on the name or path + /// of this block device. + /// + /// Note that the encrypted and the hidden volumes operate on the same storage area, so using + /// both at the same time might lead to data loss. + /// + /// The hidden volume to unlock is selected based on the provided password. + /// + /// # Errors + /// + /// - [`AesDecryptionFailed`][] if the encrypted storage has not been opened before calling + /// this method or the AES key has not been built + /// - [`InvalidString`][] if the provided password contains a null byte + /// + /// # Example + /// + /// ```no_run + /// # use nitrokey::Error; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; + /// device.enable_encrypted_volume("123445")?; + /// match device.enable_hidden_volume("hidden-pw") { + /// Ok(()) => println!("Enabled a hidden volume."), + /// Err(err) => eprintln!("Could not enable the hidden volume: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`enable_encrypted_volume`]: #method.enable_encrypted_volume + /// [`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()) + }) + } + + /// Disables a hidden storage volume. + /// + /// Once the volume is disabled, it can be no longer accessed as a block device. If no hidden + /// volume has been enabled, this method still returns a success. + /// + /// # Example + /// + /// ```no_run + /// # use nitrokey::Error; + /// + /// fn use_volume() {} + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; + /// device.enable_encrypted_volume("123445")?; + /// match device.enable_hidden_volume("hidden-pw") { + /// Ok(()) => { + /// println!("Enabled the hidden volume."); + /// use_volume(); + /// match device.disable_hidden_volume() { + /// Ok(()) => println!("Disabled the hidden volume."), + /// Err(err) => { + /// eprintln!("Could not disable the hidden volume: {}", err); + /// }, + /// }; + /// }, + /// Err(err) => eprintln!("Could not enable the hidden volume: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + pub fn disable_hidden_volume(&mut self) -> Result<(), Error> { + get_command_result(unsafe { nitrokey_sys::NK_lock_hidden_volume() }) + } + + /// Creates a hidden volume. + /// + /// The volume is crated in the given slot and in the given range of the available memory, + /// where `start` is the start position as a percentage of the available memory, and `end` is + /// the end position as a percentage of the available memory. The volume will be protected by + /// the given password. + /// + /// Note that the encrypted and the hidden volumes operate on the same storage area, so using + /// both at the same time might lead to data loss. + /// + /// According to the libnitrokey documentation, this function only works if the encrypted + /// storage has been opened. + /// + /// # Errors + /// + /// - [`AesDecryptionFailed`][] if the encrypted storage has not been opened before calling + /// this method or the AES key has not been built + /// - [`InvalidString`][] if the provided password contains a null byte + /// + /// # Example + /// + /// ```no_run + /// # use nitrokey::Error; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; + /// device.enable_encrypted_volume("123445")?; + /// device.create_hidden_volume(0, 0, 100, "hidden-pw")?; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString + pub fn create_hidden_volume( + &mut self, + slot: u8, + start: u8, + 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()) + }) + } + + /// Sets the access mode of the unencrypted volume. + /// + /// This command will reconnect the unencrypted volume so buffers should be flushed before + /// calling it. Since firmware version v0.51, this command requires the admin PIN. Older + /// firmware versions are not supported. + /// + /// # Errors + /// + /// - [`InvalidString`][] if the provided password contains a null byte + /// - [`WrongPassword`][] if the provided admin password is wrong + /// + /// # Example + /// + /// ```no_run + /// # use nitrokey::Error; + /// use nitrokey::VolumeMode; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; + /// match device.set_unencrypted_volume_mode("12345678", VolumeMode::ReadWrite) { + /// Ok(()) => println!("Set the unencrypted volume to read-write mode."), + /// Err(err) => eprintln!("Could not set the unencrypted volume to read-write mode: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString + /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword + pub fn set_unencrypted_volume_mode( + &mut self, + 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) + } + + /// Sets the access mode of the encrypted volume. + /// + /// This command will reconnect the encrypted volume so buffers should be flushed before + /// calling it. It is only available in firmware version 0.49. + /// + /// # Errors + /// + /// - [`InvalidString`][] if the provided password contains a null byte + /// - [`WrongPassword`][] if the provided admin password is wrong + /// + /// # Example + /// + /// ```no_run + /// # use nitrokey::Error; + /// use nitrokey::VolumeMode; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; + /// match device.set_encrypted_volume_mode("12345678", VolumeMode::ReadWrite) { + /// Ok(()) => println!("Set the encrypted volume to read-write mode."), + /// Err(err) => eprintln!("Could not set the encrypted volume to read-write mode: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString + /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword + pub fn set_encrypted_volume_mode( + &mut self, + 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) + } + + /// Returns the status of the connected storage device. + /// + /// # Example + /// + /// ```no_run + /// # use nitrokey::Error; + /// + /// fn use_volume() {} + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect_storage()?; + /// match device.get_status() { + /// Ok(status) => { + /// println!("SD card ID: {:#x}", status.serial_number_sd_card); + /// }, + /// Err(err) => eprintln!("Could not get Storage status: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + pub fn get_status(&self) -> Result { + let mut raw_status = nitrokey_sys::NK_storage_status { + unencrypted_volume_read_only: false, + unencrypted_volume_active: false, + encrypted_volume_read_only: false, + encrypted_volume_active: false, + hidden_volume_read_only: false, + hidden_volume_active: false, + firmware_version_major: 0, + firmware_version_minor: 0, + firmware_locked: false, + serial_number_sd_card: 0, + serial_number_smart_card: 0, + user_retry_count: 0, + admin_retry_count: 0, + new_sd_card_found: false, + filled_with_random: false, + stick_initialized: false, + }; + let raw_result = unsafe { nitrokey_sys::NK_get_status_storage(&mut raw_status) }; + get_command_result(raw_result).map(|_| StorageStatus::from(raw_status)) + } + + /// Returns the production information for the connected storage device. + /// + /// # Example + /// + /// ```no_run + /// # use nitrokey::Error; + /// + /// fn use_volume() {} + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect_storage()?; + /// match device.get_production_info() { + /// Ok(data) => { + /// println!("SD card ID: {:#x}", data.sd_card.serial_number); + /// println!("SD card size: {} GB", data.sd_card.size); + /// }, + /// Err(err) => eprintln!("Could not get Storage production info: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + pub fn get_production_info(&self) -> Result { + let mut raw_data = nitrokey_sys::NK_storage_ProductionTest { + FirmwareVersion_au8: [0, 2], + FirmwareVersionInternal_u8: 0, + SD_Card_Size_u8: 0, + CPU_CardID_u32: 0, + SmartCardID_u32: 0, + SD_CardID_u32: 0, + SC_UserPwRetryCount: 0, + SC_AdminPwRetryCount: 0, + SD_Card_ManufacturingYear_u8: 0, + SD_Card_ManufacturingMonth_u8: 0, + SD_Card_OEM_u16: 0, + SD_WriteSpeed_u16: 0, + SD_Card_Manufacturer_u8: 0, + }; + let raw_result = unsafe { nitrokey_sys::NK_get_storage_production_info(&mut raw_data) }; + get_command_result(raw_result).map(|_| StorageProductionInfo::from(raw_data)) + } + + /// Clears the warning for a new SD card. + /// + /// The Storage status contains a field for a new SD card warning. After a factory reset, the + /// field is set to true. After filling the SD card with random data, it is set to false. + /// This method can be used to set it to false without filling the SD card with random data. + /// + /// # Errors + /// + /// - [`InvalidString`][] if the provided password contains a null byte + /// - [`WrongPassword`][] if the provided admin password is wrong + /// + /// # Example + /// + /// ```no_run + /// # use nitrokey::Error; + /// + /// # fn try_main() -> Result<(), Error> { + /// let mut manager = nitrokey::take()?; + /// let mut device = manager.connect_storage()?; + /// match device.clear_new_sd_card_warning("12345678") { + /// Ok(()) => println!("Cleared the new SD card warning."), + /// Err(err) => eprintln!("Could not set the clear the new SD card warning: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`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()) + }) + } + + /// 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() }) + } + + /// Exports the firmware to the unencrypted volume. + /// + /// This command requires the admin PIN. The unencrypted volume must be in read-write mode + /// when this command is executed. Otherwise, it will still return `Ok` but not write the + /// firmware. + /// + /// This command unmounts the unencrypted volume if it has been mounted, so all buffers should + /// be flushed. The firmware is written to the `firmware.bin` file on the unencrypted volume. + /// + /// # Errors + /// + /// - [`InvalidString`][] if one of the provided passwords contains a null byte + /// - [`WrongPassword`][] if the admin password is wrong + /// + /// [`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()) }) + } +} + +impl<'a> Drop for Storage<'a> { + fn drop(&mut self) { + unsafe { + nitrokey_sys::NK_logout(); + } + } +} + +impl<'a> Device<'a> for Storage<'a> { + fn into_manager(mut self) -> &'a mut crate::Manager { + self.manager.take().unwrap() + } + + fn get_model(&self) -> Model { + Model::Storage + } +} + +impl<'a> GenerateOtp for Storage<'a> {} + +impl From 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 From 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, + } + } +} diff --git a/src/device/wrapper.rs b/src/device/wrapper.rs new file mode 100644 index 0000000..a3a18f9 --- /dev/null +++ b/src/device/wrapper.rs @@ -0,0 +1,134 @@ +// Copyright (C) 2018-2019 Robin Krahl +// SPDX-License-Identifier: MIT + +use crate::device::{Device, Model, Pro, Storage}; +use crate::error::Error; +use crate::otp::GenerateOtp; + +/// A wrapper for a Nitrokey device of unknown type. +/// +/// Use the [`connect`][] method to obtain a wrapped instance. The wrapper implements all traits +/// that are shared between all Nitrokey devices so that the shared functionality can be used +/// without knowing the type of the underlying device. If you want to use functionality that is +/// not available for all devices, you have to extract the device. +/// +/// # Examples +/// +/// Authentication with error handling: +/// +/// ```no_run +/// use nitrokey::{Authenticate, DeviceWrapper, User}; +/// # use nitrokey::Error; +/// +/// fn perform_user_task<'a>(device: &User<'a, DeviceWrapper<'a>>) {} +/// fn perform_other_task(device: &DeviceWrapper) {} +/// +/// # fn try_main() -> Result<(), Error> { +/// let mut manager = nitrokey::take()?; +/// let device = manager.connect()?; +/// let device = match device.authenticate_user("123456") { +/// Ok(user) => { +/// perform_user_task(&user); +/// user.device() +/// }, +/// Err((device, err)) => { +/// eprintln!("Could not authenticate as user: {}", err); +/// device +/// }, +/// }; +/// perform_other_task(&device); +/// # Ok(()) +/// # } +/// ``` +/// +/// Device-specific commands: +/// +/// ```no_run +/// use nitrokey::{DeviceWrapper, Storage}; +/// # use nitrokey::Error; +/// +/// fn perform_common_task(device: &DeviceWrapper) {} +/// fn perform_storage_task(device: &Storage) {} +/// +/// # fn try_main() -> Result<(), Error> { +/// let mut manager = nitrokey::take()?; +/// let device = manager.connect()?; +/// perform_common_task(&device); +/// match device { +/// DeviceWrapper::Storage(storage) => perform_storage_task(&storage), +/// _ => (), +/// }; +/// # Ok(()) +/// # } +/// ``` +/// +/// [`connect`]: struct.Manager.html#method.connect +#[derive(Debug)] +pub enum DeviceWrapper<'a> { + /// A Nitrokey Storage device. + Storage(Storage<'a>), + /// A Nitrokey Pro device. + Pro(Pro<'a>), +} + +impl<'a> DeviceWrapper<'a> { + fn device(&self) -> &dyn Device<'a> { + match *self { + DeviceWrapper::Storage(ref storage) => storage, + DeviceWrapper::Pro(ref pro) => pro, + } + } + + fn device_mut(&mut self) -> &mut dyn Device<'a> { + match *self { + DeviceWrapper::Storage(ref mut storage) => storage, + DeviceWrapper::Pro(ref mut pro) => pro, + } + } +} + +impl<'a> From> for DeviceWrapper<'a> { + fn from(device: Pro<'a>) -> Self { + DeviceWrapper::Pro(device) + } +} + +impl<'a> From> for DeviceWrapper<'a> { + fn from(device: Storage<'a>) -> Self { + DeviceWrapper::Storage(device) + } +} + +impl<'a> GenerateOtp for DeviceWrapper<'a> { + fn get_hotp_slot_name(&self, slot: u8) -> Result { + self.device().get_hotp_slot_name(slot) + } + + fn get_totp_slot_name(&self, slot: u8) -> Result { + self.device().get_totp_slot_name(slot) + } + + fn get_hotp_code(&mut self, slot: u8) -> Result { + self.device_mut().get_hotp_code(slot) + } + + fn get_totp_code(&self, slot: u8) -> Result { + self.device().get_totp_code(slot) + } +} + +impl<'a> Device<'a> for DeviceWrapper<'a> { + fn into_manager(self) -> &'a mut crate::Manager { + match self { + DeviceWrapper::Pro(dev) => dev.into_manager(), + DeviceWrapper::Storage(dev) => dev.into_manager(), + } + } + + fn get_model(&self) -> Model { + match *self { + DeviceWrapper::Pro(_) => Model::Pro, + DeviceWrapper::Storage(_) => Model::Storage, + } + } +} -- cgit v1.2.3 From 0bffc7931e011b4c0c046ed7608fbe9b7a1ffd19 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Fri, 27 Dec 2019 23:07:26 +0100 Subject: Replace rand_os::OsRng with rand_core::OsRng rand_os::OsRng has been deprecated. Instead we can use rand_core with the getrandom feature. --- Cargo.toml | 3 +-- src/util.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/Cargo.toml b/Cargo.toml index 320e69d..16bd3b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,8 +20,7 @@ exclude = [".builds/*"] lazy_static = "1.2" libc = "0.2" nitrokey-sys = "3.5" -rand_core = {version = "0.5", default-features = false, features = ["std"] } -rand_os = {version = "0.2"} +rand_core = {version = "0.5.1", features = ["getrandom"] } [dev-dependencies] nitrokey-test = "0.3" diff --git a/src/util.rs b/src/util.rs index a5dd1e5..5a56c55 100644 --- a/src/util.rs +++ b/src/util.rs @@ -5,8 +5,7 @@ use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_int}; use libc::{c_void, free}; -use rand_core::RngCore; -use rand_os::OsRng; +use rand_core::{OsRng, RngCore}; use crate::error::{Error, LibraryError}; -- cgit v1.2.3 From c35a20f6c034e4d8aa1eeba3eef85429e09d95dc Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Fri, 27 Dec 2019 23:07:56 +0100 Subject: Implement std::convert::TryFrom for RawConfig Previously, the RawConfig struct had a try_from function. As the TryFrom trait has been stabilized with Rust 1.34.0, we can use it instead. --- src/auth.rs | 1 + src/config.rs | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/auth.rs b/src/auth.rs index 0b000f7..cab1021 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,6 +1,7 @@ // Copyright (C) 2018-2019 Robin Krahl // SPDX-License-Identifier: MIT +use std::convert::TryFrom as _; use std::marker; use std::ops; use std::os::raw::c_char; diff --git a/src/config.rs b/src/config.rs index c273792..cb678d7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,8 @@ // Copyright (C) 2018-2019 Robin Krahl // SPDX-License-Identifier: MIT +use std::convert; + use crate::error::{Error, LibraryError}; /// The configuration for a Nitrokey. @@ -68,8 +70,10 @@ impl Config { } } -impl RawConfig { - pub fn try_from(config: Config) -> Result { +impl convert::TryFrom for RawConfig { + type Error = Error; + + fn try_from(config: Config) -> Result { Ok(RawConfig { numlock: option_to_config_otp_slot(config.numlock)?, capslock: option_to_config_otp_slot(config.capslock)?, -- cgit v1.2.3 From 4467ce7a2d1eedb320e92e5a26f1b93bc41d3611 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Fri, 27 Dec 2019 23:08:29 +0100 Subject: Simplify doc tests with results Since Rust 1.34.0, we no longer need a `fn main` comment in doc tests that return results. It is sufficient to have an `Ok` return value with type annotations. --- src/device/mod.rs | 4 +--- src/lib.rs | 20 +++++--------------- 2 files changed, 6 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/device/mod.rs b/src/device/mod.rs index af28ab5..5e15f08 100644 --- a/src/device/mod.rs +++ b/src/device/mod.rs @@ -78,13 +78,11 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// // ... /// } /// - /// # fn main() -> Result<(), nitrokey::Error> { /// match nitrokey::take()?.connect() { /// Ok(device) => do_something(device), /// Err(err) => println!("Could not connect to a Nitrokey: {}", err), /// } - /// # Ok(()) - /// # } + /// # Ok::<(), nitrokey::Error>(()) /// ``` fn into_manager(self) -> &'a mut crate::Manager; diff --git a/src/lib.rs b/src/lib.rs index a4402c5..059792d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -243,14 +243,12 @@ impl Manager { /// /// fn do_something(device: DeviceWrapper) {} /// - /// # fn main() -> Result<(), nitrokey::Error> { /// let mut manager = nitrokey::take()?; /// match manager.connect() { /// Ok(device) => do_something(device), /// Err(err) => println!("Could not connect to a Nitrokey: {}", err), /// } - /// # Ok(()) - /// # } + /// # Ok::<(), nitrokey::Error>(()) /// ``` /// /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected @@ -276,13 +274,11 @@ impl Manager { /// /// fn do_something(device: DeviceWrapper) {} /// - /// # fn main() -> Result<(), nitrokey::Error> { /// match nitrokey::take()?.connect_model(Model::Pro) { /// Ok(device) => do_something(device), /// Err(err) => println!("Could not connect to a Nitrokey Pro: {}", err), /// } - /// # Ok(()) - /// # } + /// # Ok::<(), nitrokey::Error>(()) /// ``` /// /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected @@ -307,13 +303,11 @@ impl Manager { /// /// fn use_pro(device: Pro) {} /// - /// # fn main() -> Result<(), nitrokey::Error> { /// match nitrokey::take()?.connect_pro() { /// Ok(device) => use_pro(device), /// Err(err) => println!("Could not connect to the Nitrokey Pro: {}", err), /// } - /// # Ok(()) - /// # } + /// # Ok::<(), nitrokey::Error>(()) /// ``` /// /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected @@ -338,13 +332,11 @@ impl Manager { /// /// fn use_storage(device: Storage) {} /// - /// # fn main() -> Result<(), nitrokey::Error> { /// match nitrokey::take()?.connect_storage() { /// Ok(device) => use_storage(device), /// Err(err) => println!("Could not connect to the Nitrokey Storage: {}", err), /// } - /// # Ok(()) - /// # } + /// # Ok::<(), nitrokey::Error>(()) /// ``` /// /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected @@ -453,11 +445,9 @@ pub fn set_log_level(level: LogLevel) { /// # Example /// /// ``` -/// # fn main() -> Result<(), nitrokey::Error> { /// let version = nitrokey::get_library_version()?; /// println!("Using libnitrokey {}", version.git); -/// # Ok(()) -/// # } +/// # Ok::<(), nitrokey::Error>(()) /// ``` /// /// [`Utf8Error`]: enum.Error.html#variant.Utf8Error -- cgit v1.2.3