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). --- tests/lib.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'tests') 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 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 'tests') 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 'tests') 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 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 'tests') 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 0ac9c401c9968d39581fe2bc3a6610cb9a7a22d8 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 27 Jan 2019 19:14:51 +0000 Subject: Use Manager in device connection tests The previous patches refactored the connection handling to use the Manager struct. This patch changes the tests to use the new Manager methods instead of the deprecated functions. --- tests/device.rs | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) (limited to 'tests') diff --git a/tests/device.rs b/tests/device.rs index 76f38e6..bb71eb4 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -33,24 +33,19 @@ fn count_nitrokey_block_devices() -> usize { #[test_device] fn connect_no_device() { - assert_cmu_err!(CommunicationError::NotConnected, nitrokey::connect()); - assert_cmu_err!( - CommunicationError::NotConnected, - nitrokey::take() - .unwrap() - .connect_model(nitrokey::Model::Pro) - ); + let mut manager = nitrokey::take().unwrap(); + + assert_cmu_err!(CommunicationError::NotConnected, manager.connect()); assert_cmu_err!( CommunicationError::NotConnected, - nitrokey::take() - .unwrap() - .connect_model(nitrokey::Model::Storage) + manager.connect_model(nitrokey::Model::Pro) ); - assert_cmu_err!(CommunicationError::NotConnected, nitrokey::Pro::connect()); assert_cmu_err!( CommunicationError::NotConnected, - nitrokey::Storage::connect() + manager.connect_model(nitrokey::Model::Storage) ); + assert_cmu_err!(CommunicationError::NotConnected, manager.connect_pro()); + assert_cmu_err!(CommunicationError::NotConnected, manager.connect_storage()); } #[test_device] @@ -58,9 +53,10 @@ fn connect_pro(device: Pro) { assert_eq!(device.get_model(), nitrokey::Model::Pro); drop(device); - assert_any_ok!(nitrokey::connect()); - assert_any_ok!(nitrokey::take().unwrap().connect_model(nitrokey::Model::Pro)); - assert_any_ok!(nitrokey::take().unwrap().connect_pro()); + let mut manager = nitrokey::take().unwrap(); + assert_any_ok!(manager.connect()); + assert_any_ok!(manager.connect_model(nitrokey::Model::Pro)); + assert_any_ok!(manager.connect_pro()); } #[test_device] @@ -68,9 +64,10 @@ fn connect_storage(device: Storage) { assert_eq!(device.get_model(), nitrokey::Model::Storage); drop(device); - assert_any_ok!(nitrokey::connect()); - assert_any_ok!(nitrokey::take().unwrap().connect_model(nitrokey::Model::Storage)); - assert_any_ok!(nitrokey::take().unwrap().connect_storage()); + let mut manager = nitrokey::take().unwrap(); + assert_any_ok!(manager.connect()); + assert_any_ok!(manager.connect_model(nitrokey::Model::Storage)); + assert_any_ok!(manager.connect_storage()); } fn assert_empty_serial_number() { -- cgit v1.2.3