diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/device.rs | 404 | ||||
-rw-r--r-- | src/lib.rs | 89 | ||||
-rw-r--r-- | src/misc.rs | 99 | ||||
-rw-r--r-- | src/tests/no_device.rs | 7 | ||||
-rw-r--r-- | src/tests/pro.rs | 27 |
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, } @@ -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(), |