aboutsummaryrefslogtreecommitdiff
path: root/src/device.rs
diff options
context:
space:
mode:
authorRobin Krahl <robin.krahl@ireas.org>2018-05-24 20:36:46 +0000
committerRobin Krahl <robin.krahl@ireas.org>2018-05-28 20:08:23 +0200
commit5f956b8e2a7f1fbf8e968154f55fadb71e8b521a (patch)
tree4a820312740cfaf6259c152bdecd02aca301c51b /src/device.rs
parent2ec913fdcadef73281ec30f96c0fc7cd00a4ed26 (diff)
downloadnitrokey-rs-5f956b8e2a7f1fbf8e968154f55fadb71e8b521a.tar.gz
nitrokey-rs-5f956b8e2a7f1fbf8e968154f55fadb71e8b521a.tar.bz2
Introduce DeviceWrapper to abstract over devices
DeviceWrapper abstracts over the supported devices. It implements the traits that are implemented by all supported devices. The previous UnauthenticatedDevice is renamed to Pro to prepare Storage support. connect_model is moved to Pro::connect.
Diffstat (limited to 'src/device.rs')
-rw-r--r--src/device.rs404
1 files changed, 235 insertions, 169 deletions
diff --git a/src/device.rs b/src/device.rs
index ab90d3d..d24bf51 100644
--- a/src/device.rs
+++ b/src/device.rs
@@ -3,6 +3,7 @@ use libc;
use nitrokey_sys;
use std::ffi::CString;
use std::os::raw::c_int;
+use misc::Authenticate;
use otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData, RawOtpSlotData};
use util::{generate_password, get_last_error, result_from_string, CommandError, CommandStatus};
@@ -10,29 +11,30 @@ static TEMPORARY_PASSWORD_LENGTH: usize = 25;
/// Available Nitrokey models.
#[derive(Debug, PartialEq)]
-pub enum Model {
+enum Model {
/// The Nitrokey Storage.
Storage,
/// The Nitrokey Pro.
Pro,
}
-/// A Nitrokey device without user or admin authentication.
+/// A wrapper for a Nitrokey device of unknown type.
///
-/// Use [`connect`][] or [`connect_model`][] to obtain an instance. If you
-/// want to execute a command that requires user or admin authentication,
-/// use [`authenticate_admin`][] or [`authenticate_user`][].
+/// Use the function [`connect`][] to obtain a wrapped instance. The wrapper implements all traits
+/// that are shared between all Nitrokey devices so that the shared functionality can be used
+/// without knowing the type of the underlying device. If you want to use functionality that is
+/// not available for all devices, you have to extract the device.
///
/// # Examples
///
/// Authentication with error handling:
///
/// ```no_run
-/// use nitrokey::{Authenticate, UnauthenticatedDevice, UserAuthenticatedDevice};
+/// use nitrokey::{Authenticate, DeviceWrapper, User};
/// # use nitrokey::CommandError;
///
-/// fn perform_user_task(device: &UserAuthenticatedDevice) {}
-/// fn perform_other_task(device: &UnauthenticatedDevice) {}
+/// fn perform_user_task(device: &User<DeviceWrapper>) {}
+/// fn perform_other_task(device: &DeviceWrapper) {}
///
/// # fn try_main() -> Result<(), CommandError> {
/// let device = nitrokey::connect()?;
@@ -51,48 +53,93 @@ pub enum Model {
/// # }
/// ```
///
+/// [`connect`]: fn.connect.html
+// TODO: add example for Storage-specific code
+#[derive(Debug)]
+pub enum DeviceWrapper {
+ /// A Nitrokey Storage device.
+ Storage(()),
+ /// A Nitrokey Pro device.
+ Pro(Pro),
+}
+
+/// 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`][].
+///
+/// # Examples
+///
+/// Authentication with error handling:
+///
+/// ```no_run
+/// use nitrokey::{Authenticate, User, Pro};
+/// # use nitrokey::CommandError;
+///
+/// fn perform_user_task(device: &User<Pro>) {}
+/// fn perform_other_task(device: &Pro) {}
+///
+/// # fn try_main() -> Result<(), CommandError> {
+/// let device = nitrokey::Pro::connect()?;
+/// let device = match device.authenticate_user("123456") {
+/// Ok(user) => {
+/// perform_user_task(&user);
+/// user.device()
+/// },
+/// Err((device, err)) => {
+/// println!("Could not authenticate as user: {:?}", err);
+/// device
+/// },
+/// };
+/// perform_other_task(&device);
+/// # Ok(())
+/// # }
+/// ```
+///
/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user
/// [`connect`]: fn.connect.html
-/// [`connect_model`]: fn.connect_model.html
+/// [`Pro::connect`]: #method.connect
#[derive(Debug)]
-pub struct UnauthenticatedDevice {}
+pub struct Pro {}
/// A Nitrokey device with user authentication.
///
-/// To obtain an instance of this struct, use the [`authenticate_user`][]
-/// method on an [`UnauthenticatedDevice`][]. To get back to an
-/// unauthenticated device, use the [`device`][] method.
+/// To obtain an instance of this struct, use the [`authenticate_user`][] method from the
+/// [`Authenticate`][] trait. To get back to an unauthenticated device, use the [`device`][]
+/// method.
///
+/// [`Authenticate`]: trait.Authenticate.html
/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
/// [`device`]: #method.device
-/// [`UnauthenticatedDevice`]: struct.UnauthenticatedDevice.html
#[derive(Debug)]
-pub struct UserAuthenticatedDevice {
- device: UnauthenticatedDevice,
+pub struct User<T: Device> {
+ device: T,
temp_password: Vec<u8>,
}
/// A Nitrokey device with admin authentication.
///
-/// To obtain an instance of this struct, use the [`authenticate_admin`][]
-/// method on an [`UnauthenticatedDevice`][]. To get back to an
-/// unauthenticated device, use the [`device`][] method.
+/// To obtain an instance of this struct, use the [`authenticate_admin`][] method from the
+/// [`Authenticate`][] trait. To get back to an unauthenticated device, use the [`device`][]
+/// method.
///
+/// [`Authenticate`]: trait.Authenticate.html
/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
/// [`device`]: #method.device
-/// [`UnauthenticatedDevice`]: struct.UnauthenticatedDevice.html
#[derive(Debug)]
-pub struct AdminAuthenticatedDevice {
- device: UnauthenticatedDevice,
+pub struct Admin<T: Device> {
+ device: T,
temp_password: Vec<u8>,
}
/// A Nitrokey device.
///
-/// This trait provides the commands that can be executed without
-/// authentication.
-pub trait Device {
+/// This trait provides the commands that can be executed without authentication and that are
+/// present on all supported Nitrokey devices.
+pub trait Device: GenerateOtp {
/// Sets the time on the Nitrokey. This command may set the time to
/// arbitrary values. `time` is the number of seconds since January 1st,
/// 1970 (Unix timestamp).
@@ -124,7 +171,7 @@ pub trait Device {
///
/// - [`Timestamp`][] if the time could not be set
///
- /// [`get_totp_code`]: trait.ProvideOtp.html#method.get_totp_code
+ /// [`get_totp_code`]: trait.GenerateOtp.html#method.get_totp_code
/// [`Timestamp`]: enum.CommandError.html#variant.Timestamp
fn set_time(&self, time: u64) -> CommandStatus {
unsafe { CommandStatus::from(nitrokey_sys::NK_totp_set_time(time)) }
@@ -350,121 +397,134 @@ pub trait Device {
}
}
-/// 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 {
- /// 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.
- ///
- /// This method generates a random temporary password that is used for all
- /// operations that require user access.
- ///
- /// # Errors
- ///
- /// - [`InvalidString`][] if the provided user password contains a null byte
- /// - [`RngError`][] if the generation of the temporary password failed
- /// - [`WrongPassword`][] if the provided user password is wrong
- ///
- /// # Example
- ///
- /// ```no_run
- /// use nitrokey::{Authenticate, UnauthenticatedDevice, UserAuthenticatedDevice};
- /// # use nitrokey::CommandError;
- ///
- /// fn perform_user_task(device: &UserAuthenticatedDevice) {}
- /// fn perform_other_task(device: &UnauthenticatedDevice) {}
- ///
- /// # fn try_main() -> Result<(), CommandError> {
- /// let device = nitrokey::connect()?;
- /// let device = match device.authenticate_user("123456") {
- /// Ok(user) => {
- /// perform_user_task(&user);
- /// user.device()
- /// },
- /// Err((device, err)) => {
- /// println!("Could not authenticate as user: {:?}", err);
- /// device
- /// },
- /// };
- /// perform_other_task(&device);
- /// # Ok(())
- /// # }
- /// ```
- ///
- /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
- /// [`RngError`]: enum.CommandError.html#variant.RngError
- /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
- fn authenticate_user(
- self,
- password: &str,
- ) -> Result<UserAuthenticatedDevice, (Self, CommandError)>
- where
- Self: Sized;
+trait AuthenticatedDevice<T> {
+ fn new(device: T, temp_password: Vec<u8>) -> Self;
+}
- /// Performs admin authentication. This method consumes the device. If
- /// successful, an authenticated device is returned. Otherwise, the
- /// current unauthenticated device and the error are returned.
- ///
- /// This method generates a random temporary password that is used for all
- /// operations that require admin access.
- ///
- /// # Errors
- ///
- /// - [`InvalidString`][] if the provided admin password contains a null byte
- /// - [`RngError`][] if the generation of the temporary password failed
- /// - [`WrongPassword`][] if the provided admin password is wrong
- ///
- /// # Example
- ///
- /// ```no_run
- /// use nitrokey::{Authenticate, AdminAuthenticatedDevice, UnauthenticatedDevice};
- /// # use nitrokey::CommandError;
- ///
- /// fn perform_admin_task(device: &AdminAuthenticatedDevice) {}
- /// fn perform_other_task(device: &UnauthenticatedDevice) {}
- ///
- /// # fn try_main() -> Result<(), CommandError> {
- /// let device = nitrokey::connect()?;
- /// let device = match device.authenticate_admin("123456") {
- /// Ok(admin) => {
- /// perform_admin_task(&admin);
- /// admin.device()
- /// },
- /// Err((device, err)) => {
- /// println!("Could not authenticate as admin: {:?}", err);
- /// device
- /// },
- /// };
- /// perform_other_task(&device);
- /// # Ok(())
- /// # }
- /// ```
- ///
- /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString
- /// [`RngError`]: enum.CommandError.html#variant.RngError
- /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
- fn authenticate_admin(
- self,
- password: &str,
- ) -> Result<AdminAuthenticatedDevice, (Self, CommandError)>
- where
- Self: Sized;
+/// Connects to a Nitrokey device. This method can be used to connect to any
+/// connected device, both a Nitrokey Pro and a Nitrokey Storage.
+///
+/// # Example
+///
+/// ```
+/// use nitrokey::DeviceWrapper;
+///
+/// fn do_something(device: DeviceWrapper) {}
+///
+/// match nitrokey::connect() {
+/// Ok(device) => do_something(device),
+/// Err(err) => println!("Could not connect to a Nitrokey: {:?}", err),
+/// }
+/// ```
+pub fn connect() -> Result<DeviceWrapper, CommandError> {
+ unsafe {
+ match nitrokey_sys::NK_login_auto() {
+ 1 => match get_connected_device() {
+ Some(wrapper) => Ok(wrapper),
+ None => Err(CommandError::Unknown),
+ },
+ _ => Err(CommandError::Unknown),
+ }
+ }
+}
+
+fn get_connected_device() -> Option<DeviceWrapper> {
+ // TODO: check connected device
+ Some(DeviceWrapper::Pro(Pro {}))
+}
+
+fn connect_model(model: Model) -> bool {
+ let model = match model {
+ Model::Storage => nitrokey_sys::NK_device_model_NK_STORAGE,
+ Model::Pro => nitrokey_sys::NK_device_model_NK_PRO,
+ };
+ unsafe { nitrokey_sys::NK_login_enum(model) == 1 }
+}
+
+impl<T: AsRef<GenerateOtp>> GenerateOtp for T {
+ fn get_hotp_slot_name(&self, slot: u8) -> Result<String, CommandError> {
+ self.as_ref().get_hotp_slot_name(slot)
+ }
+
+ fn get_totp_slot_name(&self, slot: u8) -> Result<String, CommandError> {
+ self.as_ref().get_totp_slot_name(slot)
+ }
+
+ fn get_hotp_code(&self, slot: u8) -> Result<String, CommandError> {
+ self.as_ref().get_hotp_code(slot)
+ }
+
+ fn get_totp_code(&self, slot: u8) -> Result<String, CommandError> {
+ self.as_ref().get_totp_code(slot)
+ }
}
-trait AuthenticatedDevice {
- fn new(device: UnauthenticatedDevice, temp_password: Vec<u8>) -> Self;
+impl<T: AsRef<Device> + GenerateOtp> Device for T {}
+
+impl AsRef<GenerateOtp> for DeviceWrapper {
+ fn as_ref(&self) -> &(GenerateOtp + 'static) {
+ match *self {
+ DeviceWrapper::Storage(_) => panic!("..."),
+ DeviceWrapper::Pro(ref pro) => pro,
+ }
+ }
+}
+
+impl AsRef<Device> for DeviceWrapper {
+ fn as_ref(&self) -> &(Device + 'static) {
+ match *self {
+ DeviceWrapper::Storage(_) => panic!("..."),
+ DeviceWrapper::Pro(ref pro) => pro,
+ }
+ }
}
-impl UnauthenticatedDevice {
- fn authenticate<D, T>(
- self,
- password: &str,
- callback: T,
- ) -> Result<D, (UnauthenticatedDevice, CommandError)>
+impl Authenticate for DeviceWrapper {
+ fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, CommandError)> {
+ match self {
+ DeviceWrapper::Storage(_) => panic!("..."),
+ DeviceWrapper::Pro(pro) => {
+ let result = pro.authenticate_user(password);
+ match result {
+ Ok(user) => Ok(User::new(
+ DeviceWrapper::Pro(user.device),
+ user.temp_password,
+ )),
+ Err((pro, err)) => Err((DeviceWrapper::Pro(pro), err)),
+ }
+ }
+ }
+ }
+
+ fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, CommandError)> {
+ match self {
+ DeviceWrapper::Storage(_) => panic!("..."),
+ DeviceWrapper::Pro(pro) => {
+ let result = pro.authenticate_admin(password);
+ match result {
+ Ok(admin) => Ok(Admin::new(
+ DeviceWrapper::Pro(admin.device),
+ admin.temp_password,
+ )),
+ Err((pro, err)) => Err((DeviceWrapper::Pro(pro), err)),
+ }
+ }
+ }
+ }
+}
+
+impl Pro {
+ pub fn connect() -> Result<Pro, CommandError> {
+ // TODO: maybe Option instead of Result?
+ match connect_model(Model::Pro) {
+ true => Ok(Pro {}),
+ false => Err(CommandError::Unknown),
+ }
+ }
+
+ fn authenticate<D, T>(self, password: &str, callback: T) -> Result<D, (Self, CommandError)>
where
- D: AuthenticatedDevice,
+ D: AuthenticatedDevice<Pro>,
T: Fn(*const i8, *const i8) -> c_int,
{
let temp_password = match generate_password(TEMPORARY_PASSWORD_LENGTH) {
@@ -486,27 +546,21 @@ impl UnauthenticatedDevice {
}
}
-impl Authenticate for UnauthenticatedDevice {
- fn authenticate_user(
- self,
- password: &str,
- ) -> Result<UserAuthenticatedDevice, (Self, CommandError)> {
+impl Authenticate for Pro {
+ fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, CommandError)> {
return self.authenticate(password, |password_ptr, temp_password_ptr| unsafe {
nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr)
});
}
- fn authenticate_admin(
- self,
- password: &str,
- ) -> Result<AdminAuthenticatedDevice, (Self, CommandError)> {
+ fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, CommandError)> {
return self.authenticate(password, |password_ptr, temp_password_ptr| unsafe {
nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr)
});
}
}
-impl Drop for UnauthenticatedDevice {
+impl Drop for Pro {
fn drop(&mut self) {
unsafe {
nitrokey_sys::NK_logout();
@@ -514,22 +568,26 @@ impl Drop for UnauthenticatedDevice {
}
}
-impl Device for UnauthenticatedDevice {}
+impl Device for Pro {}
-impl GenerateOtp for UnauthenticatedDevice {}
+impl GenerateOtp for Pro {}
-impl UserAuthenticatedDevice {
+impl<T: Device + 'static> AsRef<Device> for User<T> {
+ fn as_ref(&self) -> &(Device + 'static) {
+ &self.device
+ }
+}
+
+impl<T: Device> User<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.
- pub fn device(self) -> UnauthenticatedDevice {
+ pub fn device(self) -> T {
self.device
}
}
-impl Device for UserAuthenticatedDevice {}
-
-impl GenerateOtp for UserAuthenticatedDevice {
+impl<T: Device> GenerateOtp for User<T> {
/// Generates an HOTP code on the given slot. This operation may not
/// require user authorization, depending on the device configuration (see
/// [`get_config`][]).
@@ -558,7 +616,7 @@ impl GenerateOtp for UserAuthenticatedDevice {
/// # }
/// ```
///
- /// [`get_config`]: #method.get_config
+ /// [`get_config`]: trait.Device.html#method.get_config
/// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
/// [`WrongSlot`]: enum.CommandError.html#variant.WrongSlot
fn get_hotp_code(&self, slot: u8) -> Result<String, CommandError> {
@@ -599,7 +657,7 @@ impl GenerateOtp for UserAuthenticatedDevice {
/// # }
/// ```
///
- /// [`get_config`]: #method.get_config
+ /// [`get_config`]: trait.Device.html#method.get_config
/// [`set_time`]: trait.Device.html#method.set_time
/// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed
/// [`WrongSlot`]: enum.CommandError.html#variant.WrongSlot
@@ -617,20 +675,32 @@ impl GenerateOtp for UserAuthenticatedDevice {
}
}
-impl AuthenticatedDevice for UserAuthenticatedDevice {
- fn new(device: UnauthenticatedDevice, temp_password: Vec<u8>) -> Self {
- UserAuthenticatedDevice {
+impl<T: Device> AuthenticatedDevice<T> for User<T> {
+ fn new(device: T, temp_password: Vec<u8>) -> Self {
+ User {
device,
temp_password,
}
}
}
-impl AdminAuthenticatedDevice {
+impl<T: Device + 'static> AsRef<GenerateOtp> for Admin<T> {
+ fn as_ref(&self) -> &(GenerateOtp + 'static) {
+ &self.device
+ }
+}
+
+impl<T: Device + 'static> AsRef<Device> for Admin<T> {
+ fn as_ref(&self) -> &(Device + 'static) {
+ &self.device
+ }
+}
+
+impl<T: Device> Admin<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.
- pub fn device(self) -> UnauthenticatedDevice {
+ pub fn device(self) -> T {
self.device
}
@@ -680,9 +750,9 @@ impl AdminAuthenticatedDevice {
}
}
- fn write_otp_slot<T>(&self, data: OtpSlotData, callback: T) -> CommandStatus
+ fn write_otp_slot<C>(&self, data: OtpSlotData, callback: C) -> CommandStatus
where
- T: Fn(RawOtpSlotData, *const i8) -> c_int,
+ C: Fn(RawOtpSlotData, *const i8) -> c_int,
{
let raw_data = match RawOtpSlotData::new(data) {
Ok(raw_data) => raw_data,
@@ -694,9 +764,7 @@ impl AdminAuthenticatedDevice {
}
}
-impl Device for AdminAuthenticatedDevice {}
-
-impl ConfigureOtp for AdminAuthenticatedDevice {
+impl<T: Device> ConfigureOtp for Admin<T> {
fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> CommandStatus {
return self.write_otp_slot(data, |raw_data: RawOtpSlotData, temp_password_ptr| unsafe {
nitrokey_sys::NK_write_hotp_slot(
@@ -740,11 +808,9 @@ impl ConfigureOtp for AdminAuthenticatedDevice {
}
}
-impl GenerateOtp for AdminAuthenticatedDevice {}
-
-impl AuthenticatedDevice for AdminAuthenticatedDevice {
- fn new(device: UnauthenticatedDevice, temp_password: Vec<u8>) -> Self {
- AdminAuthenticatedDevice {
+impl<T: Device> AuthenticatedDevice<T> for Admin<T> {
+ fn new(device: T, temp_password: Vec<u8>) -> Self {
+ Admin {
device,
temp_password,
}