summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/auth.rs90
-rw-r--r--src/device.rs368
-rw-r--r--src/error.rs30
-rw-r--r--src/lib.rs301
-rw-r--r--src/otp.rs27
-rw-r--r--src/pws.rs54
6 files changed, 569 insertions, 301 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 f6492cd..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),
@@ -399,14 +427,7 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp + fmt::Debug {
fn get_firmware_version(&self) -> Result<FirmwareVersion, Error> {
let major = result_or_error(unsafe { nitrokey_sys::NK_get_major_firmware_version() })?;
let minor = result_or_error(unsafe { nitrokey_sys::NK_get_minor_firmware_version() })?;
- let max = i32::from(u8::max_value());
- if major < 0 || minor < 0 || major > max || minor > max {
- return Err(Error::UnexpectedError);
- }
- Ok(FirmwareVersion {
- major: major as u8,
- minor: minor as u8,
- })
+ Ok(FirmwareVersion { major, minor })
}
/// Returns the current configuration of the Nitrokey device.
@@ -418,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);
@@ -452,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),
@@ -485,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),
@@ -518,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),
@@ -552,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),
@@ -583,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),
@@ -617,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),
@@ -633,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),
@@ -702,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,
@@ -721,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,
@@ -737,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)
}
@@ -767,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,
@@ -776,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();
@@ -821,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<'a> GenerateOtp for Pro<'a> {}
-impl Storage {
- /// Connects to a Nitrokey Storage.
- ///
- /// # Errors
- ///
- /// - [`NotConnected`][] if no Nitrokey device of the given model is connected
- ///
- /// # Example
- ///
- /// ```
- /// use nitrokey::Storage;
- ///
- /// fn use_storage(device: Storage) {}
- ///
- /// match nitrokey::Storage::connect() {
- /// Ok(device) => use_storage(device),
- /// Err(err) => eprintln!("Could not connect to the Nitrokey Storage: {}", err),
- /// }
- /// ```
- ///
- /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
- 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())
- }
- }
-
- 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),
}
}
@@ -882,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),
@@ -919,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),
@@ -953,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),
@@ -982,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.");
@@ -1028,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."),
@@ -1061,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(()) => {
@@ -1108,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(())
@@ -1148,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),
@@ -1193,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),
@@ -1231,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);
@@ -1274,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);
@@ -1322,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),
@@ -1367,7 +1307,7 @@ impl Storage {
}
}
-impl Drop for Storage {
+impl<'a> Drop for Storage<'a> {
fn drop(&mut self) {
unsafe {
nitrokey_sys::NK_logout();
@@ -1375,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),
diff --git a/src/lib.rs b/src/lib.rs
index c35829c..a4402c5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -9,13 +9,16 @@
//! performed without authentication, some require user access, and some require admin access.
//! This is modelled using the types [`User`][] and [`Admin`][].
//!
-//! Use [`connect`][] to connect to any Nitrokey device. The method will return a
+//! You can only connect to one Nitrokey at a time. Use the global [`take`][] function to obtain
+//! an reference to the [`Manager`][] singleton that keeps track of the connections. Then use the
+//! [`connect`][] method to connect to any Nitrokey device. The method will return a
//! [`DeviceWrapper`][] that abstracts over the supported Nitrokey devices. You can also use
-//! [`Pro::connect`][] or [`Storage::connect`][] to connect to a specific device.
+//! [`connect_model`][], [`connect_pro`][] or [`connect_storage`][] to connect to a specific
+//! device.
//!
-//! You can then use [`authenticate_user`][] or [`authenticate_admin`][] to get an authenticated
-//! device that can perform operations that require authentication. You can use [`device`][] to go
-//! back to the unauthenticated device.
+//! You can call [`authenticate_user`][] or [`authenticate_admin`][] to get an authenticated device
+//! that can perform operations that require authentication. You can use [`device`][] to go back
+//! to the unauthenticated device.
//!
//! This makes sure that you can only execute a command if you have the required access rights.
//! Otherwise, your code will not compile. The only exception are the methods to generate one-time
@@ -31,7 +34,8 @@
//! # use nitrokey::Error;
//!
//! # fn try_main() -> Result<(), Error> {
-//! let device = nitrokey::connect()?;
+//! let mut manager = nitrokey::take()?;
+//! let device = manager.connect()?;
//! println!("{}", device.get_serial_number()?);
//! # Ok(())
//! # }
@@ -44,7 +48,8 @@
//! # use nitrokey::Error;
//!
//! # fn try_main() -> Result<(), Error> {
-//! let device = nitrokey::connect()?;
+//! let mut manager = nitrokey::take()?;
+//! let device = manager.connect()?;
//! let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits);
//! match device.authenticate_admin("12345678") {
//! Ok(mut admin) => {
@@ -66,7 +71,8 @@
//! # use nitrokey::Error;
//!
//! # fn try_main() -> Result<(), Error> {
-//! let mut device = nitrokey::connect()?;
+//! let mut manager = nitrokey::take()?;
+//! let mut device = manager.connect()?;
//! match device.get_hotp_code(1) {
//! Ok(code) => println!("Generated HOTP code: {}", code),
//! Err(err) => eprintln!("Could not generate HOTP code: {}", err),
@@ -77,9 +83,12 @@
//!
//! [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
//! [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user
-//! [`connect`]: fn.connect.html
-//! [`Pro::connect`]: struct.Pro.html#fn.connect.html
-//! [`Storage::connect`]: struct.Storage.html#fn.connect.html
+//! [`take`]: fn.take.html
+//! [`connect`]: struct.Manager.html#method.connect
+//! [`connect_model`]: struct.Manager.html#method.connect_model
+//! [`connect_pro`]: struct.Manager.html#method.connect_pro
+//! [`connect_storage`]: struct.Manager.html#method.connect_storage
+//! [`manager`]: trait.Device.html#method.manager
//! [`device`]: struct.User.html#method.device
//! [`get_hotp_code`]: trait.GenerateOtp.html#method.get_hotp_code
//! [`get_totp_code`]: trait.GenerateOtp.html#method.get_totp_code
@@ -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,16 @@ 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;
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 +131,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 +165,263 @@ 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`][]. If you want to access the manager instance even if the cache is poisoned,
+/// use [`force_take`][].
+///
+/// # Errors
+///
+/// - [`ConcurrentAccessError`][] if the token for the `Manager` instance cannot be locked
+/// - [`PoisonError`][] if the lock is poisoned
+///
+/// [`take_blocking`]: fn.take_blocking.html
+/// [`force_take`]: fn.force_take.html
+/// [`ConcurrentAccessError`]: struct.Error.html#variant.ConcurrentAccessError
+/// [`PoisonError`]: struct.Error.html#variant.PoisonError
+/// [`Manager`]: struct.Manager.html
+pub fn take() -> Result<sync::MutexGuard<'static, Manager>, Error> {
+ MANAGER.try_lock().map_err(Into::into)
+}
+
+/// Try to take an instance of the connection manager, ignoring a poisoned cache.
+///
+/// There may only be one [`Manager`][] instance at the same time. If there already is an
+/// instance, a [`ConcurrentAccessError`][] is returned. If you want a blocking version, use
+/// [`take_blocking`][].
+///
+/// If a thread has previously panicked while accessing the manager instance, the cache is
+/// poisoned. The default implementation, [`take`][], returns a [`PoisonError`][] on subsequent
+/// calls. This implementation ignores the poisoned cache and returns the manager instance.
+///
+/// # Errors
+///
+/// - [`ConcurrentAccessError`][] if the token for the `Manager` instance cannot be locked
+///
+/// [`take`]: fn.take.html
+/// [`take_blocking`]: fn.take_blocking.html
+/// [`ConcurrentAccessError`]: struct.Error.html#variant.ConcurrentAccessError
+/// [`Manager`]: struct.Manager.html
+pub fn force_take() -> Result<sync::MutexGuard<'static, Manager>, Error> {
+ match take() {
+ Ok(guard) => Ok(guard),
+ Err(err) => match err {
+ Error::PoisonError(err) => Ok(err.into_inner()),
+ err => Err(err),
+ },
+ }
+}
+
/// Enables or disables debug output. Calling this method with `true` is equivalent to setting the
/// log level to `Debug`; calling it with `false` is equivalent to the log level `Error` (see
/// [`set_log_level`][]).
diff --git a/src/otp.rs b/src/otp.rs
index ee142c7..4667aff 100644
--- a/src/otp.rs
+++ b/src/otp.rs
@@ -35,7 +35,8 @@ pub trait ConfigureOtp {
/// # use nitrokey::Error;
///
/// # fn try_main() -> Result<(), Error> {
- /// let device = nitrokey::connect()?;
+ /// let mut manager = nitrokey::take()?;
+ /// let device = manager.connect()?;
/// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::SixDigits);
/// match device.authenticate_admin("12345678") {
/// Ok(mut admin) => {
@@ -71,7 +72,8 @@ pub trait ConfigureOtp {
/// # use nitrokey::Error;
///
/// # fn try_main() -> Result<(), Error> {
- /// let device = nitrokey::connect()?;
+ /// let mut manager = nitrokey::take()?;
+ /// let device = manager.connect()?;
/// let slot_data = OtpSlotData::new(1, "test", "01234567890123456689", OtpMode::EightDigits);
/// match device.authenticate_admin("12345678") {
/// Ok(mut admin) => {
@@ -104,7 +106,8 @@ pub trait ConfigureOtp {
/// # use nitrokey::Error;
///
/// # fn try_main() -> Result<(), Error> {
- /// let device = nitrokey::connect()?;
+ /// let mut manager = nitrokey::take()?;
+ /// let device = manager.connect()?;
/// match device.authenticate_admin("12345678") {
/// Ok(mut admin) => {
/// match admin.erase_hotp_slot(1) {
@@ -134,7 +137,8 @@ pub trait ConfigureOtp {
/// # use nitrokey::Error;
///
/// # fn try_main() -> Result<(), Error> {
- /// let device = nitrokey::connect()?;
+ /// let mut manager = nitrokey::take()?;
+ /// let device = manager.connect()?;
/// match device.authenticate_admin("12345678") {
/// Ok(mut admin) => {
/// match admin.erase_totp_slot(1) {
@@ -171,7 +175,8 @@ pub trait GenerateOtp {
/// # use nitrokey::Error;
///
/// # fn try_main() -> Result<(), Error> {
- /// let mut device = nitrokey::connect()?;
+ /// let mut manager = nitrokey::take()?;
+ /// let mut device = manager.connect()?;
/// let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH);
/// match time {
/// Ok(time) => device.set_time(time.as_secs(), false)?,
@@ -209,7 +214,8 @@ pub trait GenerateOtp {
/// use nitrokey::{CommandError, Error, GenerateOtp};
///
/// # fn try_main() -> Result<(), Error> {
- /// let device = nitrokey::connect()?;
+ /// let mut manager = nitrokey::take()?;
+ /// let device = manager.connect()?;
/// match device.get_hotp_slot_name(1) {
/// Ok(name) => println!("HOTP slot 1: {}", name),
/// Err(Error::CommandError(CommandError::SlotNotProgrammed)) => eprintln!("HOTP slot 1 not programmed"),
@@ -238,7 +244,8 @@ pub trait GenerateOtp {
/// use nitrokey::{CommandError, Error, GenerateOtp};
///
/// # fn try_main() -> Result<(), Error> {
- /// let device = nitrokey::connect()?;
+ /// let mut manager = nitrokey::take()?;
+ /// let device = manager.connect()?;
/// match device.get_totp_slot_name(1) {
/// Ok(name) => println!("TOTP slot 1: {}", name),
/// Err(Error::CommandError(CommandError::SlotNotProgrammed)) => eprintln!("TOTP slot 1 not programmed"),
@@ -270,7 +277,8 @@ pub trait GenerateOtp {
/// # use nitrokey::Error;
///
/// # fn try_main() -> Result<(), Error> {
- /// let mut device = nitrokey::connect()?;
+ /// let mut manager = nitrokey::take()?;
+ /// let mut device = manager.connect()?;
/// let code = device.get_hotp_code(1)?;
/// println!("Generated HOTP code on slot 1: {}", code);
/// # Ok(())
@@ -305,7 +313,8 @@ pub trait GenerateOtp {
/// # use nitrokey::Error;
///
/// # fn try_main() -> Result<(), Error> {
- /// let mut device = nitrokey::connect()?;
+ /// let mut manager = nitrokey::take()?;
+ /// let mut device = manager.connect()?;
/// let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH);
/// match time {
/// Ok(time) => {
diff --git a/src/pws.rs b/src/pws.rs
index 371de6e..3398deb 100644
--- a/src/pws.rs
+++ b/src/pws.rs
@@ -43,7 +43,8 @@ pub const SLOT_COUNT: u8 = 16;
/// }
///
/// # fn try_main() -> Result<(), Error> {
-/// let mut device = nitrokey::connect()?;
+/// let mut manager = nitrokey::take()?;
+/// let mut device = manager.connect()?;
/// let pws = device.get_password_safe("123456")?;
/// use_password_safe(&pws);
/// drop(pws);
@@ -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)
}
}