diff options
| -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(), | 
