From 5f956b8e2a7f1fbf8e968154f55fadb71e8b521a Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 24 May 2018 20:36:46 +0000 Subject: 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. --- src/device.rs | 404 ++++++++++++++++++++++++++++--------------------- src/lib.rs | 89 +++-------- src/misc.rs | 99 ++++++++++++ src/tests/no_device.rs | 7 +- src/tests/pro.rs | 27 ++-- 5 files changed, 371 insertions(+), 255 deletions(-) create mode 100644 src/misc.rs (limited to 'src') 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) {} +/// 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) {} +/// 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 { + device: T, temp_password: Vec, } /// 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 { + device: T, temp_password: Vec, } /// 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 - where - Self: Sized; +trait AuthenticatedDevice { + fn new(device: T, temp_password: Vec) -> 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 - 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 { + 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 { + // 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> GenerateOtp for T { + fn get_hotp_slot_name(&self, slot: u8) -> Result { + self.as_ref().get_hotp_slot_name(slot) + } + + fn get_totp_slot_name(&self, slot: u8) -> Result { + self.as_ref().get_totp_slot_name(slot) + } + + fn get_hotp_code(&self, slot: u8) -> Result { + self.as_ref().get_hotp_code(slot) + } + + fn get_totp_code(&self, slot: u8) -> Result { + self.as_ref().get_totp_code(slot) + } } -trait AuthenticatedDevice { - fn new(device: UnauthenticatedDevice, temp_password: Vec) -> Self; +impl + GenerateOtp> Device for T {} + +impl AsRef for DeviceWrapper { + fn as_ref(&self) -> &(GenerateOtp + 'static) { + match *self { + DeviceWrapper::Storage(_) => panic!("..."), + DeviceWrapper::Pro(ref pro) => pro, + } + } +} + +impl AsRef for DeviceWrapper { + fn as_ref(&self) -> &(Device + 'static) { + match *self { + DeviceWrapper::Storage(_) => panic!("..."), + DeviceWrapper::Pro(ref pro) => pro, + } + } } -impl UnauthenticatedDevice { - fn authenticate( - self, - password: &str, - callback: T, - ) -> Result +impl Authenticate for DeviceWrapper { + fn authenticate_user(self, password: &str) -> Result, (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, (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 { + // TODO: maybe Option instead of Result? + match connect_model(Model::Pro) { + true => Ok(Pro {}), + false => Err(CommandError::Unknown), + } + } + + fn authenticate(self, password: &str, callback: T) -> Result where - D: AuthenticatedDevice, + D: AuthenticatedDevice, 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 { +impl Authenticate for Pro { + fn authenticate_user(self, password: &str) -> Result, (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 { + fn authenticate_admin(self, password: &str) -> Result, (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 AsRef for User { + fn as_ref(&self) -> &(Device + 'static) { + &self.device + } +} + +impl User { /// 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 GenerateOtp for User { /// 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 { @@ -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) -> Self { - UserAuthenticatedDevice { +impl AuthenticatedDevice for User { + fn new(device: T, temp_password: Vec) -> Self { + User { device, temp_password, } } } -impl AdminAuthenticatedDevice { +impl AsRef for Admin { + fn as_ref(&self) -> &(GenerateOtp + 'static) { + &self.device + } +} + +impl AsRef for Admin { + fn as_ref(&self) -> &(Device + 'static) { + &self.device + } +} + +impl Admin { /// 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(&self, data: OtpSlotData, callback: T) -> CommandStatus + fn write_otp_slot(&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 ConfigureOtp for Admin { 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) -> Self { - AdminAuthenticatedDevice { +impl AuthenticatedDevice for Admin { + fn new(device: T, temp_password: Vec) -> 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 { - 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 { - 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) {} + /// 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, (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) {} + /// 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, (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 { 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(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(), -- cgit v1.2.3