aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/device.rs404
-rw-r--r--src/lib.rs89
-rw-r--r--src/misc.rs99
-rw-r--r--src/tests/no_device.rs7
-rw-r--r--src/tests/pro.rs27
5 files changed, 371 insertions, 255 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,
}
diff --git a/src/lib.rs b/src/lib.rs
index cb44ee2..03ef1ea 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,16 +2,17 @@
//!
//! # Usage
//!
-//! Operations on the Nitrokey require different authentication levels. Some
-//! operations can be performed without authentication, some require user
-//! access, and some require admin access. This is modelled using the types
-//! [`UnauthenticatedDevice`][], [`UserAuthenticatedDevice`][] and
-//! [`AdminAuthenticatedDevice`][].
+//! Operations on the Nitrokey require different authentication levels. Some operations can be
+//! performed without authentication, some require user access, and some require admin access.
+//! This is modelled using the types [`User`][] and [`Admin`][].
//!
-//! Use [`connect`][] or [`connect_model`][] to obtain an
-//! [`UnauthenticatedDevice`][]. You can then use [`authenticate_user`][] or
-//! [`authenticate_admin`][] to get an authenticated device. You can then use
-//! [`device`][] to go back to the unauthenticated device.
+//! Use [`connect`][] 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`][] 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.
//!
//! 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
@@ -76,13 +77,13 @@
//! [`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
-//! [`device`]: struct.AuthenticatedDevice.html#method.device
-//! [`get_hotp_code`]: trait.ProvideOtp.html#method.get_hotp_code
-//! [`get_totp_code`]: trait.ProvideOtp.html#method.get_totp_code
-//! [`AdminAuthenticatedDevice`]: struct.AdminAuthenticatedDevice.html
-//! [`UserAuthenticatedDevice`]: struct.UserAuthenticatedDevice.html
-//! [`UnauthenticatedDevice`]: struct.UnauthenticatedDevice.html
+//! [`Pro::connect`]: struct.Pro.html#fn.connect.html
+//! [`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
+//! [`Admin`]: struct.Admin.html
+//! [`DeviceWrapper`]: enum.DeviceWrapper.html
+//! [`User`]: struct.User.html
extern crate libc;
extern crate nitrokey_sys;
@@ -90,68 +91,18 @@ extern crate rand;
mod config;
mod device;
+mod misc;
mod otp;
mod util;
#[cfg(test)]
mod tests;
pub use config::Config;
-pub use device::{AdminAuthenticatedDevice, Authenticate, Device, Model, UnauthenticatedDevice,
- UserAuthenticatedDevice};
+pub use device::{connect, Admin, Device, DeviceWrapper, Pro, User};
+pub use misc::Authenticate;
pub use otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData};
pub use util::{CommandError, CommandStatus, LogLevel};
-/// 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::UnauthenticatedDevice;
-///
-/// fn do_something(device: UnauthenticatedDevice) {}
-///
-/// match nitrokey::connect() {
-/// Ok(device) => do_something(device),
-/// Err(err) => println!("Could not connect to a Nitrokey: {:?}", err),
-/// }
-/// ```
-pub fn connect() -> Result<UnauthenticatedDevice, CommandError> {
- unsafe {
- match nitrokey_sys::NK_login_auto() {
- 1 => Ok(UnauthenticatedDevice {}),
- _ => Err(CommandError::Unknown),
- }
- }
-}
-
-/// Connects to a Nitrokey device of the given model.
-///
-/// # Example
-///
-/// ```
-/// use nitrokey::{Model, UnauthenticatedDevice};
-///
-/// fn do_something(device: UnauthenticatedDevice) {}
-///
-/// match nitrokey::connect_model(Model::Pro) {
-/// Ok(device) => do_something(device),
-/// Err(err) => println!("Could not connect to a Nitrokey Pro: {:?}", err),
-/// }
-/// ```
-pub fn connect_model(model: Model) -> Result<UnauthenticatedDevice, CommandError> {
- let model = match model {
- Model::Storage => nitrokey_sys::NK_device_model_NK_STORAGE,
- Model::Pro => nitrokey_sys::NK_device_model_NK_PRO,
- };
- unsafe {
- return match nitrokey_sys::NK_login_enum(model) {
- 1 => Ok(UnauthenticatedDevice {}),
- rv => Err(CommandError::from(rv)),
- };
- }
-}
-
/// 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/misc.rs b/src/misc.rs
new file mode 100644
index 0000000..42f8639
--- /dev/null
+++ b/src/misc.rs
@@ -0,0 +1,99 @@
+use device::{Admin, Device, User};
+use util::CommandError;
+
+/// 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, DeviceWrapper, User};
+ /// # use nitrokey::CommandError;
+ ///
+ /// fn perform_user_task(device: &User<DeviceWrapper>) {}
+ /// fn perform_other_task(device: &DeviceWrapper) {}
+ ///
+ /// # 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<User<Self>, (Self, CommandError)>
+ where
+ Self: Device + Sized;
+
+ /// 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, Admin, DeviceWrapper};
+ /// # use nitrokey::CommandError;
+ ///
+ /// fn perform_admin_task(device: &Admin<DeviceWrapper>) {}
+ /// fn perform_other_task(device: &DeviceWrapper) {}
+ ///
+ /// # 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<Admin<Self>, (Self, CommandError)>
+ where
+ Self: Device + Sized;
+}
diff --git a/src/tests/no_device.rs b/src/tests/no_device.rs
index 04c5c8a..4118bcd 100644
--- a/src/tests/no_device.rs
+++ b/src/tests/no_device.rs
@@ -1,9 +1,8 @@
-use Model;
-
#[test]
#[cfg_attr(not(feature = "test-no-device"), ignore)]
fn connect() {
assert!(::connect().is_err());
- assert!(::connect_model(Model::Storage).is_err());
- assert!(::connect_model(Model::Pro).is_err());
+ assert!(::Pro::connect().is_err());
+ // TODO: test storage
+ // assert!(::Storage::connect().is_err());
}
diff --git a/src/tests/pro.rs b/src/tests/pro.rs
index 8476451..915b45b 100644
--- a/src/tests/pro.rs
+++ b/src/tests/pro.rs
@@ -1,6 +1,6 @@
use std::ffi::CStr;
-use {AdminAuthenticatedDevice, Authenticate, CommandError, CommandStatus, Config, ConfigureOtp,
- Device, GenerateOtp, Model, OtpMode, OtpSlotData, UnauthenticatedDevice};
+use {Admin, Authenticate, CommandError, CommandStatus, Config, ConfigureOtp, Device, GenerateOtp,
+ OtpMode, OtpSlotData, Pro};
static ADMIN_PASSWORD: &str = "12345678";
static ADMIN_NEW_PASSWORD: &str = "1234567890";
@@ -25,11 +25,11 @@ static TOTP_CODES: &[(u64, &str)] = &[
(20000000000, "65353130"),
];
-fn get_test_device() -> UnauthenticatedDevice {
- ::connect_model(Model::Pro).expect("Could not connect to the Nitrokey Pro.")
+fn get_test_device() -> Pro {
+ Pro::connect().expect("Could not connect to the Nitrokey Pro.")
}
-fn get_admin_test_device() -> AdminAuthenticatedDevice {
+fn get_admin_test_device() -> Admin<Pro> {
get_test_device()
.authenticate_admin(ADMIN_PASSWORD)
.expect("Could not login as admin.")
@@ -39,8 +39,9 @@ fn get_admin_test_device() -> AdminAuthenticatedDevice {
#[cfg_attr(not(feature = "test-pro"), ignore)]
fn connect() {
assert!(::connect().is_ok());
- assert!(::connect_model(Model::Pro).is_ok());
- assert!(::connect_model(Model::Storage).is_err());
+ assert!(Pro::connect().is_ok());
+ // TODO: test storage
+ // assert!(::Storage::connect().is_err());
}
fn assert_empty_serial_number() {
@@ -55,14 +56,14 @@ fn assert_empty_serial_number() {
#[test]
#[cfg_attr(not(feature = "test-pro"), ignore)]
fn disconnect() {
- ::connect().unwrap();
+ Pro::connect().unwrap();
assert_empty_serial_number();
- ::connect()
+ Pro::connect()
.unwrap()
.authenticate_admin(ADMIN_PASSWORD)
.unwrap();
assert_empty_serial_number();
- ::connect()
+ Pro::connect()
.unwrap()
.authenticate_user(USER_PASSWORD)
.unwrap();
@@ -85,7 +86,7 @@ fn configure_hotp(admin: &ConfigureOtp) {
assert_eq!(CommandStatus::Success, admin.write_hotp_slot(slot_data, 0));
}
-fn check_hotp_codes<T: GenerateOtp>(device: &T) {
+fn check_hotp_codes(device: &GenerateOtp) {
for code in HOTP_CODES {
let result = device.get_hotp_code(1);
assert_eq!(code, &result.unwrap());
@@ -287,7 +288,7 @@ fn get_firmware_version() {
assert!(minor == 7 || minor == 8);
}
-fn admin_retry(device: UnauthenticatedDevice, suffix: &str, count: u8) -> UnauthenticatedDevice {
+fn admin_retry(device: Pro, suffix: &str, count: u8) -> Pro {
let result = device.authenticate_admin(&(ADMIN_PASSWORD.to_owned() + suffix));
let device = match result {
Ok(admin) => admin.device(),
@@ -297,7 +298,7 @@ fn admin_retry(device: UnauthenticatedDevice, suffix: &str, count: u8) -> Unauth
return device;
}
-fn user_retry(device: UnauthenticatedDevice, suffix: &str, count: u8) -> UnauthenticatedDevice {
+fn user_retry(device: Pro, suffix: &str, count: u8) -> Pro {
let result = device.authenticate_user(&(USER_PASSWORD.to_owned() + suffix));
let device = match result {
Ok(admin) => admin.device(),