summaryrefslogtreecommitdiff
path: root/src/device.rs
diff options
context:
space:
mode:
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,
}