diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/auth.rs | 90 | ||||
| -rw-r--r-- | src/device.rs | 359 | ||||
| -rw-r--r-- | src/error.rs | 30 | ||||
| -rw-r--r-- | src/lib.rs | 272 | ||||
| -rw-r--r-- | src/otp.rs | 27 | ||||
| -rw-r--r-- | src/pws.rs | 54 | 
6 files changed, 539 insertions, 293 deletions
| diff --git a/src/auth.rs b/src/auth.rs index f9f50fa..0b000f7 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,6 +1,7 @@  // Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>  // 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. @@ -38,11 +39,12 @@ pub trait Authenticate {      /// use nitrokey::{Authenticate, DeviceWrapper, User};      /// # use nitrokey::Error;      /// -    /// fn perform_user_task(device: &User<DeviceWrapper>) {} +    /// 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); @@ -61,9 +63,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<User<Self>, (Self, Error)> +    fn authenticate_user(self, password: &str) -> Result<User<'a, Self>, (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 @@ -84,11 +86,12 @@ pub trait Authenticate {      /// use nitrokey::{Authenticate, Admin, DeviceWrapper};      /// # use nitrokey::Error;      /// -    /// fn perform_admin_task(device: &Admin<DeviceWrapper>) {} +    /// 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); @@ -107,9 +110,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<Admin<Self>, (Self, Error)> +    fn authenticate_admin(self, password: &str) -> Result<Admin<'a, Self>, (Self, Error)>      where -        Self: Device + Sized; +        Self: Device<'a> + Sized;  }  trait AuthenticatedDevice<T> { @@ -128,9 +131,10 @@ trait AuthenticatedDevice<T> {  /// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin  /// [`device`]: #method.device  #[derive(Debug)] -pub struct User<T: Device> { +pub struct User<'a, T: Device<'a>> {      device: T,      temp_password: Vec<u8>, +    marker: marker::PhantomData<&'a T>,  }  /// A Nitrokey device with admin authentication. @@ -143,14 +147,15 @@ pub struct User<T: Device> {  /// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin  /// [`device`]: #method.device  #[derive(Debug)] -pub struct Admin<T: Device> { +pub struct Admin<'a, T: Device<'a>> {      device: T,      temp_password: Vec<u8>, +    marker: marker::PhantomData<&'a T>,  } -fn authenticate<D, A, T>(device: D, password: &str, callback: T) -> Result<A, (D, Error)> +fn authenticate<'a, D, A, T>(device: D, password: &str, callback: T) -> Result<A, (D, Error)>  where -    D: Device, +    D: Device<'a>,      A: AuthenticatedDevice<D>,      T: Fn(*const c_char, *const c_char) -> c_int,  { @@ -170,14 +175,14 @@ where      }  } -fn authenticate_user_wrapper<T, C>( +fn authenticate_user_wrapper<'a, T, C>(      device: T,      constructor: C,      password: &str, -) -> Result<User<DeviceWrapper>, (DeviceWrapper, Error)> +) -> Result<User<'a, DeviceWrapper<'a>>, (DeviceWrapper<'a>, Error)>  where -    T: Device, -    C: Fn(T) -> DeviceWrapper, +    T: Device<'a> + 'a, +    C: Fn(T) -> DeviceWrapper<'a>,  {      let result = device.authenticate_user(password);      match result { @@ -186,14 +191,14 @@ where      }  } -fn authenticate_admin_wrapper<T, C>( +fn authenticate_admin_wrapper<'a, T, C>(      device: T,      constructor: C,      password: &str, -) -> Result<Admin<DeviceWrapper>, (DeviceWrapper, Error)> +) -> Result<Admin<'a, DeviceWrapper<'a>>, (DeviceWrapper<'a>, Error)>  where -    T: Device, -    C: Fn(T) -> DeviceWrapper, +    T: Device<'a> + 'a, +    C: Fn(T) -> DeviceWrapper<'a>,  {      let result = device.authenticate_admin(password);      match result { @@ -202,7 +207,7 @@ where      }  } -impl<T: Device> User<T> { +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 +216,7 @@ impl<T: Device> User<T> {      }  } -impl<T: Device> ops::Deref for User<T> { +impl<'a, T: Device<'a>> ops::Deref for User<'a, T> {      type Target = T;      fn deref(&self) -> &Self::Target { @@ -219,13 +224,13 @@ impl<T: Device> ops::Deref for User<T> {      }  } -impl<T: Device> ops::DerefMut for User<T> { +impl<'a, T: Device<'a>> ops::DerefMut for User<'a, T> {      fn deref_mut(&mut self) -> &mut T {          &mut self.device      }  } -impl<T: Device> GenerateOtp for User<T> { +impl<'a, T: Device<'a>> GenerateOtp for User<'a, T> {      fn get_hotp_code(&mut self, slot: u8) -> Result<String, Error> {          result_from_string(unsafe {              nitrokey_sys::NK_get_hotp_code_PIN(slot, self.temp_password_ptr()) @@ -239,11 +244,12 @@ impl<T: Device> GenerateOtp for User<T> {      }  } -impl<T: Device> AuthenticatedDevice<T> for User<T> { +impl<'a, T: Device<'a>> AuthenticatedDevice<T> for User<'a, T> {      fn new(device: T, temp_password: Vec<u8>) -> Self {          User {              device,              temp_password, +            marker: marker::PhantomData,          }      } @@ -252,7 +258,7 @@ impl<T: Device> AuthenticatedDevice<T> for User<T> {      }  } -impl<T: Device> ops::Deref for Admin<T> { +impl<'a, T: Device<'a>> ops::Deref for Admin<'a, T> {      type Target = T;      fn deref(&self) -> &Self::Target { @@ -260,13 +266,13 @@ impl<T: Device> ops::Deref for Admin<T> {      }  } -impl<T: Device> ops::DerefMut for Admin<T> { +impl<'a, T: Device<'a>> ops::DerefMut for Admin<'a, T> {      fn deref_mut(&mut self) -> &mut T {          &mut self.device      }  } -impl<T: Device> Admin<T> { +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. @@ -287,7 +293,8 @@ impl<T: Device> Admin<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) => { @@ -316,7 +323,7 @@ impl<T: Device> Admin<T> {      }  } -impl<T: Device> ConfigureOtp for Admin<T> { +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 +371,12 @@ impl<T: Device> ConfigureOtp for Admin<T> {      }  } -impl<T: Device> AuthenticatedDevice<T> for Admin<T> { +impl<'a, T: Device<'a>> AuthenticatedDevice<T> for Admin<'a, T> {      fn new(device: T, temp_password: Vec<u8>) -> Self {          Admin {              device,              temp_password, +            marker: marker::PhantomData,          }      } @@ -377,8 +385,8 @@ impl<T: Device> AuthenticatedDevice<T> for Admin<T> {      }  } -impl Authenticate for DeviceWrapper { -    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, Error)> { +impl<'a> Authenticate<'a> for DeviceWrapper<'a> { +    fn authenticate_user(self, password: &str) -> Result<User<'a, Self>, (Self, Error)> {          match self {              DeviceWrapper::Storage(storage) => {                  authenticate_user_wrapper(storage, DeviceWrapper::Storage, password) @@ -387,7 +395,7 @@ impl Authenticate for DeviceWrapper {          }      } -    fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, Error)> { +    fn authenticate_admin(self, password: &str) -> Result<Admin<'a, Self>, (Self, Error)> {          match self {              DeviceWrapper::Storage(storage) => {                  authenticate_admin_wrapper(storage, DeviceWrapper::Storage, password) @@ -399,28 +407,28 @@ impl Authenticate for DeviceWrapper {      }  } -impl Authenticate for Pro { -    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, Error)> { +impl<'a> Authenticate<'a> for Pro<'a> { +    fn authenticate_user(self, password: &str) -> Result<User<'a, Self>, (Self, Error)> {          authenticate(self, password, |password_ptr, temp_password_ptr| unsafe {              nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr)          })      } -    fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, Error)> { +    fn authenticate_admin(self, password: &str) -> Result<Admin<'a, Self>, (Self, Error)> {          authenticate(self, password, |password_ptr, temp_password_ptr| unsafe {              nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr)          })      }  } -impl Authenticate for Storage { -    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, Error)> { +impl<'a> Authenticate<'a> for Storage<'a> { +    fn authenticate_user(self, password: &str) -> Result<User<'a, Self>, (Self, Error)> {          authenticate(self, password, |password_ptr, temp_password_ptr| unsafe {              nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr)          })      } -    fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, Error)> { +    fn authenticate_admin(self, password: &str) -> Result<Admin<'a, Self>, (Self, Error)> {          authenticate(self, password, |password_ptr, temp_password_ptr| unsafe {              nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr)          }) diff --git a/src/device.rs b/src/device.rs index 51551c2..758d4c1 100644 --- a/src/device.rs +++ b/src/device.rs @@ -2,7 +2,6 @@  // SPDX-License-Identifier: MIT  use std::fmt; -use std::marker;  use libc;  use nitrokey_sys; @@ -54,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. @@ -67,11 +66,12 @@ impl fmt::Display for VolumeMode {  /// use nitrokey::{Authenticate, DeviceWrapper, User};  /// # use nitrokey::Error;  /// -/// fn perform_user_task(device: &User<DeviceWrapper>) {} +/// 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); @@ -97,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), @@ -107,21 +108,20 @@ impl fmt::Display for VolumeMode {  /// # }  /// ```  /// -/// [`connect`]: fn.connect.html +/// [`connect`]: struct.Manager.html#method.connect  #[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.  /// -/// 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  /// @@ -131,11 +131,12 @@ pub enum DeviceWrapper {  /// use nitrokey::{Authenticate, User, Pro};  /// # use nitrokey::Error;  /// -/// fn perform_user_task(device: &User<Pro>) {} +/// 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); @@ -153,21 +154,18 @@ pub enum DeviceWrapper {  ///  /// [`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 { -    // make sure that users cannot directly instantiate this type -    #[doc(hidden)] -    marker: marker::PhantomData<()>, +pub struct Pro<'a> { +    manager: Option<&'a mut crate::Manager>,  }  /// 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  /// @@ -177,11 +175,12 @@ pub struct Pro {  /// use nitrokey::{Authenticate, User, Storage};  /// # use nitrokey::Error;  /// -/// fn perform_user_task(device: &User<Storage>) {} +/// 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); @@ -199,13 +198,11 @@ pub struct Pro {  ///  /// [`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 { -    // make sure that users cannot directly instantiate this type -    #[doc(hidden)] -    marker: marker::PhantomData<()>, +pub struct Storage<'a> { +    manager: Option<&'a mut crate::Manager>,  }  /// The status of a volume on a Nitrokey Storage device. @@ -296,7 +293,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 @@ -306,7 +328,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {      /// # 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(())      /// # } @@ -322,7 +345,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {      /// # 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), @@ -344,7 +368,9 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {      /// # 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), @@ -366,7 +392,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {      /// # 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), @@ -388,7 +415,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {      /// # 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), @@ -411,7 +439,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {      /// # 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); @@ -445,7 +474,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {      /// # 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), @@ -478,7 +508,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {      /// # 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), @@ -511,7 +542,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {      /// # 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), @@ -545,7 +577,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {      /// # 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), @@ -576,7 +609,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {      /// # 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), @@ -610,7 +644,8 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {      /// # 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), @@ -626,67 +661,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 -pub fn connect() -> Result<DeviceWrapper, Error> { -    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()) -    } -} - -/// 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<DeviceWrapper, Error> { -    if connect_enum(model) { -        Ok(create_device_wrapper(model)) -    } else { -        Err(CommunicationError::NotConnected.into()) -    } -} -  fn get_connected_model() -> Option<Model> {      match unsafe { nitrokey_sys::NK_get_device_model() } {          nitrokey_sys::NK_device_model_NK_PRO => Some(Model::Pro), @@ -695,18 +669,26 @@ fn get_connected_model() -> Option<Model> {      }  } -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(),      }  } -fn get_connected_device() -> Option<DeviceWrapper> { -    get_connected_model().map(create_device_wrapper) +pub(crate) fn get_connected_device( +    manager: &mut crate::Manager, +) -> Result<DeviceWrapper<'_>, Error> { +    match get_connected_model() { +        Some(model) => Ok(create_device_wrapper(manager, model)), +        None => Err(CommunicationError::NotConnected.into()), +    }  } -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, @@ -714,15 +696,15 @@ fn connect_enum(model: Model) -> bool {      unsafe { nitrokey_sys::NK_login_enum(model) == 1 }  } -impl DeviceWrapper { -    fn device(&self) -> &dyn Device { +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 { +    fn device_mut(&mut self) -> &mut dyn Device<'a> {          match *self {              DeviceWrapper::Storage(ref mut storage) => storage,              DeviceWrapper::Pro(ref mut pro) => pro, @@ -730,19 +712,19 @@ impl DeviceWrapper {      }  } -impl From<Pro> for DeviceWrapper { -    fn from(device: Pro) -> Self { +impl<'a> From<Pro<'a>> for DeviceWrapper<'a> { +    fn from(device: Pro<'a>) -> Self {          DeviceWrapper::Pro(device)      }  } -impl From<Storage> for DeviceWrapper { -    fn from(device: Storage) -> Self { +impl<'a> From<Storage<'a>> 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<String, Error> {          self.device().get_hotp_slot_name(slot)      } @@ -760,7 +742,14 @@ impl GenerateOtp for DeviceWrapper {      }  } -impl Device for DeviceWrapper { +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, @@ -769,44 +758,15 @@ 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 -    pub fn connect() -> Result<Pro, Error> { -        // TODO: maybe Option instead of Result? -        if connect_enum(Model::Pro) { -            Ok(Pro::new()) -        } else { -            Err(CommunicationError::NotConnected.into()) -        } -    } - -    fn new() -> Pro { +impl<'a> Pro<'a> { +    pub(crate) fn new(manager: &'a mut crate::Manager) -> Pro<'a> {          Pro { -            marker: marker::PhantomData, +            manager: Some(manager),          }      }  } -impl Drop for Pro { +impl<'a> Drop for Pro<'a> {      fn drop(&mut self) {          unsafe {              nitrokey_sys::NK_logout(); @@ -814,47 +774,22 @@ impl Drop for Pro {      }  } -impl Device for Pro { +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 GenerateOtp for Pro {} - -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 -    pub fn connect() -> Result<Storage, Error> { -        // TODO: maybe Option instead of Result? -        if connect_enum(Model::Storage) { -            Ok(Storage::new()) -        } else { -            Err(CommunicationError::NotConnected.into()) -        } -    } +impl<'a> GenerateOtp for Pro<'a> {} -    fn new() -> Storage { +impl<'a> Storage<'a> { +    pub(crate) fn new(manager: &'a mut crate::Manager) -> Storage<'a> {          Storage { -            marker: marker::PhantomData, +            manager: Some(manager),          }      } @@ -875,7 +810,8 @@ impl Storage {      /// # 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), @@ -912,7 +848,8 @@ impl Storage {      /// # 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), @@ -946,7 +883,8 @@ impl Storage {      /// # 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), @@ -975,7 +913,8 @@ impl Storage {      /// 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."); @@ -1021,7 +960,8 @@ impl Storage {      /// # 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."), @@ -1054,7 +994,8 @@ impl Storage {      /// 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(()) => { @@ -1101,7 +1042,8 @@ impl Storage {      /// # 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(()) @@ -1141,7 +1083,8 @@ impl Storage {      /// 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), @@ -1186,7 +1129,8 @@ impl Storage {      /// 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), @@ -1224,7 +1168,8 @@ impl Storage {      /// 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); @@ -1267,7 +1212,8 @@ impl Storage {      /// 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); @@ -1315,7 +1261,8 @@ impl Storage {      /// # 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), @@ -1360,7 +1307,7 @@ impl Storage {      }  } -impl Drop for Storage { +impl<'a> Drop for Storage<'a> {      fn drop(&mut self) {          unsafe {              nitrokey_sys::NK_logout(); @@ -1368,13 +1315,17 @@ impl Drop for Storage {      }  } -impl Device for Storage { +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 GenerateOtp for Storage {} +impl<'a> GenerateOtp for Storage<'a> {}  impl From<nitrokey_sys::NK_storage_ProductionTest> for StorageProductionInfo {      fn from(data: nitrokey_sys::NK_storage_ProductionTest) -> Self { diff --git a/src/error.rs b/src/error.rs index 1730171..9e6adc0 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(sync::PoisonError<sync::MutexGuard<'static, crate::Manager>>), +    /// An error that occurred during random number generation.      RandError(Box<dyn error::Error>),      /// An error that is caused by an unexpected value returned by libnitrokey.      UnexpectedError, @@ -65,7 +70,22 @@ impl From<str::Utf8Error> for Error {      }  } -impl<T: device::Device> From<(T, Error)> for Error { +impl From<sync::PoisonError<sync::MutexGuard<'static, crate::Manager>>> for Error { +    fn from(error: sync::PoisonError<sync::MutexGuard<'static, crate::Manager>>) -> Self { +        Error::PoisonError(error) +    } +} + +impl From<sync::TryLockError<sync::MutexGuard<'static, crate::Manager>>> for Error { +    fn from(error: sync::TryLockError<sync::MutexGuard<'static, crate::Manager>>) -> Self { +        match error { +            sync::TryLockError::Poisoned(err) => err.into(), +            sync::TryLockError::WouldBlock => Error::ConcurrentAccessError, +        } +    } +} + +impl<'a, T: device::Device<'a>> 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(ref err) => Some(err),              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), @@ -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 @@ -89,6 +98,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,14 +110,17 @@ mod pws;  mod util;  use std::fmt; +use std::marker; +use std::sync;  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, +    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}; @@ -117,6 +132,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<Manager> = 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 +166,233 @@ 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 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<()>, +} + +impl Manager { +    fn new() -> Self { +        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> { +    /// let mut manager = nitrokey::take()?; +    /// match manager.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<DeviceWrapper<'_>, Error> { +        if unsafe { nitrokey_sys::NK_login_auto() } == 1 { +            device::get_connected_device(self) +        } else { +            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<DeviceWrapper<'_>, Error> { +        if device::connect_enum(model) { +            Ok(device::create_device_wrapper(self, model)) +        } else { +            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<Pro<'_>, Error> { +        if device::connect_enum(device::Model::Pro) { +            Ok(device::Pro::new(self)) +        } 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<Storage<'_>, Error> { +        if device::connect_enum(Model::Storage) { +            Ok(Storage::new(self)) +        } else { +            Err(CommunicationError::NotConnected.into()) +        } +    } +} + +/// 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<sync::MutexGuard<'static, Manager>, 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<sync::MutexGuard<'static, Manager>, 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`][]). @@ -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) => { @@ -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); @@ -57,8 +58,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 +68,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 @@ -98,7 +99,8 @@ pub trait GetPasswordSafe {      /// 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); @@ -117,13 +119,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<PasswordSafe<'_>, Error>; +    fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_, 'a>, 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<PasswordSafe<'a>, Error> { +) -> Result<PasswordSafe<'a, 'b>, Error> {      let user_pin_string = get_cstring(user_pin)?;      get_command_result(unsafe { nitrokey_sys::NK_enable_password_safe(user_pin_string.as_ptr()) })          .map(|_| PasswordSafe { _device: device }) @@ -137,7 +139,7 @@ fn get_pws_result(s: String) -> Result<String, Error> {      }  } -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. @@ -149,7 +151,8 @@ impl<'a> PasswordSafe<'a> {      /// # 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> PasswordSafe<'a> {      /// # 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> PasswordSafe<'a> {      /// # 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> PasswordSafe<'a> {      /// # 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> PasswordSafe<'a> {      /// # 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> PasswordSafe<'a> {      /// # 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."), @@ -357,27 +365,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 GetPasswordSafe for Pro { -    fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_>, Error> { +impl<'a> GetPasswordSafe<'a> for Pro<'a> { +    fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_, 'a>, Error> {          get_password_safe(self, user_pin)      }  } -impl GetPasswordSafe for Storage { -    fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_>, Error> { +impl<'a> GetPasswordSafe<'a> for Storage<'a> { +    fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_, 'a>, Error> {          get_password_safe(self, user_pin)      }  } -impl GetPasswordSafe for DeviceWrapper { -    fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_>, Error> { +impl<'a> GetPasswordSafe<'a> for DeviceWrapper<'a> { +    fn get_password_safe(&mut self, user_pin: &str) -> Result<PasswordSafe<'_, 'a>, Error> {          get_password_safe(self, user_pin)      }  } | 
