diff options
Diffstat (limited to 'src/device.rs')
-rw-r--r-- | src/device.rs | 404 |
1 files changed, 235 insertions, 169 deletions
diff --git a/src/device.rs b/src/device.rs index ab90d3d..d24bf51 100644 --- a/src/device.rs +++ b/src/device.rs @@ -3,6 +3,7 @@ use libc; use nitrokey_sys; use std::ffi::CString; use std::os::raw::c_int; +use misc::Authenticate; use otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData, RawOtpSlotData}; use util::{generate_password, get_last_error, result_from_string, CommandError, CommandStatus}; @@ -10,29 +11,30 @@ static TEMPORARY_PASSWORD_LENGTH: usize = 25; /// Available Nitrokey models. #[derive(Debug, PartialEq)] -pub enum Model { +enum Model { /// The Nitrokey Storage. Storage, /// The Nitrokey Pro. Pro, } -/// A Nitrokey device without user or admin authentication. +/// A wrapper for a Nitrokey device of unknown type. /// -/// Use [`connect`][] or [`connect_model`][] to obtain an instance. If you -/// want to execute a command that requires user or admin authentication, -/// use [`authenticate_admin`][] or [`authenticate_user`][]. +/// Use the function [`connect`][] to obtain a wrapped instance. The wrapper implements all traits +/// that are shared between all Nitrokey devices so that the shared functionality can be used +/// without knowing the type of the underlying device. If you want to use functionality that is +/// not available for all devices, you have to extract the device. /// /// # Examples /// /// Authentication with error handling: /// /// ```no_run -/// use nitrokey::{Authenticate, UnauthenticatedDevice, UserAuthenticatedDevice}; +/// use nitrokey::{Authenticate, DeviceWrapper, User}; /// # use nitrokey::CommandError; /// -/// fn perform_user_task(device: &UserAuthenticatedDevice) {} -/// fn perform_other_task(device: &UnauthenticatedDevice) {} +/// fn perform_user_task(device: &User<DeviceWrapper>) {} +/// fn perform_other_task(device: &DeviceWrapper) {} /// /// # fn try_main() -> Result<(), CommandError> { /// let device = nitrokey::connect()?; @@ -51,48 +53,93 @@ pub enum Model { /// # } /// ``` /// +/// [`connect`]: fn.connect.html +// TODO: add example for Storage-specific code +#[derive(Debug)] +pub enum DeviceWrapper { + /// A Nitrokey Storage device. + Storage(()), + /// A Nitrokey Pro device. + Pro(Pro), +} + +/// A Nitrokey Pro device without user or admin authentication. +/// +/// Use the global function [`connect`][] to obtain an instance wrapper or the method +/// [`connect`][`Pro::connect`] to directly obtain an instance. If you want to execute a command +/// that requires user or admin authentication, use [`authenticate_admin`][] or +/// [`authenticate_user`][]. +/// +/// # Examples +/// +/// Authentication with error handling: +/// +/// ```no_run +/// use nitrokey::{Authenticate, User, Pro}; +/// # use nitrokey::CommandError; +/// +/// fn perform_user_task(device: &User<Pro>) {} +/// fn perform_other_task(device: &Pro) {} +/// +/// # fn try_main() -> Result<(), CommandError> { +/// let device = nitrokey::Pro::connect()?; +/// let device = match device.authenticate_user("123456") { +/// Ok(user) => { +/// perform_user_task(&user); +/// user.device() +/// }, +/// Err((device, err)) => { +/// println!("Could not authenticate as user: {:?}", err); +/// device +/// }, +/// }; +/// perform_other_task(&device); +/// # Ok(()) +/// # } +/// ``` +/// /// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin /// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user /// [`connect`]: fn.connect.html -/// [`connect_model`]: fn.connect_model.html +/// [`Pro::connect`]: #method.connect #[derive(Debug)] -pub struct UnauthenticatedDevice {} +pub struct Pro {} /// A Nitrokey device with user authentication. /// -/// To obtain an instance of this struct, use the [`authenticate_user`][] -/// method on an [`UnauthenticatedDevice`][]. To get back to an -/// unauthenticated device, use the [`device`][] method. +/// To obtain an instance of this struct, use the [`authenticate_user`][] method from the +/// [`Authenticate`][] trait. To get back to an unauthenticated device, use the [`device`][] +/// method. /// +/// [`Authenticate`]: trait.Authenticate.html /// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin /// [`device`]: #method.device -/// [`UnauthenticatedDevice`]: struct.UnauthenticatedDevice.html #[derive(Debug)] -pub struct UserAuthenticatedDevice { - device: UnauthenticatedDevice, +pub struct User<T: Device> { + device: T, temp_password: Vec<u8>, } /// A Nitrokey device with admin authentication. /// -/// To obtain an instance of this struct, use the [`authenticate_admin`][] -/// method on an [`UnauthenticatedDevice`][]. To get back to an -/// unauthenticated device, use the [`device`][] method. +/// To obtain an instance of this struct, use the [`authenticate_admin`][] method from the +/// [`Authenticate`][] trait. To get back to an unauthenticated device, use the [`device`][] +/// method. /// +/// [`Authenticate`]: trait.Authenticate.html /// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin /// [`device`]: #method.device -/// [`UnauthenticatedDevice`]: struct.UnauthenticatedDevice.html #[derive(Debug)] -pub struct AdminAuthenticatedDevice { - device: UnauthenticatedDevice, +pub struct Admin<T: Device> { + device: T, temp_password: Vec<u8>, } /// A Nitrokey device. /// -/// This trait provides the commands that can be executed without -/// authentication. -pub trait Device { +/// This trait provides the commands that can be executed without authentication and that are +/// present on all supported Nitrokey devices. +pub trait Device: GenerateOtp { /// Sets the time on the Nitrokey. This command may set the time to /// arbitrary values. `time` is the number of seconds since January 1st, /// 1970 (Unix timestamp). @@ -124,7 +171,7 @@ pub trait Device { /// /// - [`Timestamp`][] if the time could not be set /// - /// [`get_totp_code`]: trait.ProvideOtp.html#method.get_totp_code + /// [`get_totp_code`]: trait.GenerateOtp.html#method.get_totp_code /// [`Timestamp`]: enum.CommandError.html#variant.Timestamp fn set_time(&self, time: u64) -> CommandStatus { unsafe { CommandStatus::from(nitrokey_sys::NK_totp_set_time(time)) } @@ -350,121 +397,134 @@ pub trait Device { } } -/// Provides methods to authenticate as a user or as an admin using a PIN. The authenticated -/// methods will consume the current device instance. On success, they return the authenticated -/// device. Otherwise, they return the current unauthenticated device and the error code. -pub trait Authenticate { - /// Performs user authentication. This method consumes the device. If - /// successful, an authenticated device is returned. Otherwise, the - /// current unauthenticated device and the error are returned. - /// - /// This method generates a random temporary password that is used for all - /// operations that require user access. - /// - /// # Errors - /// - /// - [`InvalidString`][] if the provided user password contains a null byte - /// - [`RngError`][] if the generation of the temporary password failed - /// - [`WrongPassword`][] if the provided user password is wrong - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::{Authenticate, UnauthenticatedDevice, UserAuthenticatedDevice}; - /// # use nitrokey::CommandError; - /// - /// fn perform_user_task(device: &UserAuthenticatedDevice) {} - /// fn perform_other_task(device: &UnauthenticatedDevice) {} - /// - /// # fn try_main() -> Result<(), CommandError> { - /// let device = nitrokey::connect()?; - /// let device = match device.authenticate_user("123456") { - /// Ok(user) => { - /// perform_user_task(&user); - /// user.device() - /// }, - /// Err((device, err)) => { - /// println!("Could not authenticate as user: {:?}", err); - /// device - /// }, - /// }; - /// perform_other_task(&device); - /// # Ok(()) - /// # } - /// ``` - /// - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString - /// [`RngError`]: enum.CommandError.html#variant.RngError - /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn authenticate_user( - self, - password: &str, - ) -> Result<UserAuthenticatedDevice, (Self, CommandError)> - where - Self: Sized; +trait AuthenticatedDevice<T> { + fn new(device: T, temp_password: Vec<u8>) -> Self; +} - /// Performs admin authentication. This method consumes the device. If - /// successful, an authenticated device is returned. Otherwise, the - /// current unauthenticated device and the error are returned. - /// - /// This method generates a random temporary password that is used for all - /// operations that require admin access. - /// - /// # Errors - /// - /// - [`InvalidString`][] if the provided admin password contains a null byte - /// - [`RngError`][] if the generation of the temporary password failed - /// - [`WrongPassword`][] if the provided admin password is wrong - /// - /// # Example - /// - /// ```no_run - /// use nitrokey::{Authenticate, AdminAuthenticatedDevice, UnauthenticatedDevice}; - /// # use nitrokey::CommandError; - /// - /// fn perform_admin_task(device: &AdminAuthenticatedDevice) {} - /// fn perform_other_task(device: &UnauthenticatedDevice) {} - /// - /// # fn try_main() -> Result<(), CommandError> { - /// let device = nitrokey::connect()?; - /// let device = match device.authenticate_admin("123456") { - /// Ok(admin) => { - /// perform_admin_task(&admin); - /// admin.device() - /// }, - /// Err((device, err)) => { - /// println!("Could not authenticate as admin: {:?}", err); - /// device - /// }, - /// }; - /// perform_other_task(&device); - /// # Ok(()) - /// # } - /// ``` - /// - /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString - /// [`RngError`]: enum.CommandError.html#variant.RngError - /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword - fn authenticate_admin( - self, - password: &str, - ) -> Result<AdminAuthenticatedDevice, (Self, CommandError)> - where - Self: Sized; +/// Connects to a Nitrokey device. This method can be used to connect to any +/// connected device, both a Nitrokey Pro and a Nitrokey Storage. +/// +/// # Example +/// +/// ``` +/// use nitrokey::DeviceWrapper; +/// +/// fn do_something(device: DeviceWrapper) {} +/// +/// match nitrokey::connect() { +/// Ok(device) => do_something(device), +/// Err(err) => println!("Could not connect to a Nitrokey: {:?}", err), +/// } +/// ``` +pub fn connect() -> Result<DeviceWrapper, CommandError> { + unsafe { + match nitrokey_sys::NK_login_auto() { + 1 => match get_connected_device() { + Some(wrapper) => Ok(wrapper), + None => Err(CommandError::Unknown), + }, + _ => Err(CommandError::Unknown), + } + } +} + +fn get_connected_device() -> Option<DeviceWrapper> { + // TODO: check connected device + Some(DeviceWrapper::Pro(Pro {})) +} + +fn connect_model(model: Model) -> bool { + let model = match model { + Model::Storage => nitrokey_sys::NK_device_model_NK_STORAGE, + Model::Pro => nitrokey_sys::NK_device_model_NK_PRO, + }; + unsafe { nitrokey_sys::NK_login_enum(model) == 1 } +} + +impl<T: AsRef<GenerateOtp>> GenerateOtp for T { + fn get_hotp_slot_name(&self, slot: u8) -> Result<String, CommandError> { + self.as_ref().get_hotp_slot_name(slot) + } + + fn get_totp_slot_name(&self, slot: u8) -> Result<String, CommandError> { + self.as_ref().get_totp_slot_name(slot) + } + + fn get_hotp_code(&self, slot: u8) -> Result<String, CommandError> { + self.as_ref().get_hotp_code(slot) + } + + fn get_totp_code(&self, slot: u8) -> Result<String, CommandError> { + self.as_ref().get_totp_code(slot) + } } -trait AuthenticatedDevice { - fn new(device: UnauthenticatedDevice, temp_password: Vec<u8>) -> Self; +impl<T: AsRef<Device> + GenerateOtp> Device for T {} + +impl AsRef<GenerateOtp> for DeviceWrapper { + fn as_ref(&self) -> &(GenerateOtp + 'static) { + match *self { + DeviceWrapper::Storage(_) => panic!("..."), + DeviceWrapper::Pro(ref pro) => pro, + } + } +} + +impl AsRef<Device> for DeviceWrapper { + fn as_ref(&self) -> &(Device + 'static) { + match *self { + DeviceWrapper::Storage(_) => panic!("..."), + DeviceWrapper::Pro(ref pro) => pro, + } + } } -impl UnauthenticatedDevice { - fn authenticate<D, T>( - self, - password: &str, - callback: T, - ) -> Result<D, (UnauthenticatedDevice, CommandError)> +impl Authenticate for DeviceWrapper { + fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, CommandError)> { + match self { + DeviceWrapper::Storage(_) => panic!("..."), + DeviceWrapper::Pro(pro) => { + let result = pro.authenticate_user(password); + match result { + Ok(user) => Ok(User::new( + DeviceWrapper::Pro(user.device), + user.temp_password, + )), + Err((pro, err)) => Err((DeviceWrapper::Pro(pro), err)), + } + } + } + } + + fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, CommandError)> { + match self { + DeviceWrapper::Storage(_) => panic!("..."), + DeviceWrapper::Pro(pro) => { + let result = pro.authenticate_admin(password); + match result { + Ok(admin) => Ok(Admin::new( + DeviceWrapper::Pro(admin.device), + admin.temp_password, + )), + Err((pro, err)) => Err((DeviceWrapper::Pro(pro), err)), + } + } + } + } +} + +impl Pro { + pub fn connect() -> Result<Pro, CommandError> { + // TODO: maybe Option instead of Result? + match connect_model(Model::Pro) { + true => Ok(Pro {}), + false => Err(CommandError::Unknown), + } + } + + fn authenticate<D, T>(self, password: &str, callback: T) -> Result<D, (Self, CommandError)> where - D: AuthenticatedDevice, + D: AuthenticatedDevice<Pro>, T: Fn(*const i8, *const i8) -> c_int, { let temp_password = match generate_password(TEMPORARY_PASSWORD_LENGTH) { @@ -486,27 +546,21 @@ impl UnauthenticatedDevice { } } -impl Authenticate for UnauthenticatedDevice { - fn authenticate_user( - self, - password: &str, - ) -> Result<UserAuthenticatedDevice, (Self, CommandError)> { +impl Authenticate for Pro { + fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, CommandError)> { return self.authenticate(password, |password_ptr, temp_password_ptr| unsafe { nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr) }); } - fn authenticate_admin( - self, - password: &str, - ) -> Result<AdminAuthenticatedDevice, (Self, CommandError)> { + fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, CommandError)> { return self.authenticate(password, |password_ptr, temp_password_ptr| unsafe { nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr) }); } } -impl Drop for UnauthenticatedDevice { +impl Drop for Pro { fn drop(&mut self) { unsafe { nitrokey_sys::NK_logout(); @@ -514,22 +568,26 @@ impl Drop for UnauthenticatedDevice { } } -impl Device for UnauthenticatedDevice {} +impl Device for Pro {} -impl GenerateOtp for UnauthenticatedDevice {} +impl GenerateOtp for Pro {} -impl UserAuthenticatedDevice { +impl<T: Device + 'static> AsRef<Device> for User<T> { + fn as_ref(&self) -> &(Device + 'static) { + &self.device + } +} + +impl<T: Device> User<T> { /// Forgets the user authentication and returns an unauthenticated /// device. This method consumes the authenticated device. It does not /// perform any actual commands on the Nitrokey. - pub fn device(self) -> UnauthenticatedDevice { + pub fn device(self) -> T { self.device } } -impl Device for UserAuthenticatedDevice {} - -impl GenerateOtp for UserAuthenticatedDevice { +impl<T: Device> GenerateOtp for User<T> { /// Generates an HOTP code on the given slot. This operation may not /// require user authorization, depending on the device configuration (see /// [`get_config`][]). @@ -558,7 +616,7 @@ impl GenerateOtp for UserAuthenticatedDevice { /// # } /// ``` /// - /// [`get_config`]: #method.get_config + /// [`get_config`]: trait.Device.html#method.get_config /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed /// [`WrongSlot`]: enum.CommandError.html#variant.WrongSlot fn get_hotp_code(&self, slot: u8) -> Result<String, CommandError> { @@ -599,7 +657,7 @@ impl GenerateOtp for UserAuthenticatedDevice { /// # } /// ``` /// - /// [`get_config`]: #method.get_config + /// [`get_config`]: trait.Device.html#method.get_config /// [`set_time`]: trait.Device.html#method.set_time /// [`SlotNotProgrammed`]: enum.CommandError.html#variant.SlotNotProgrammed /// [`WrongSlot`]: enum.CommandError.html#variant.WrongSlot @@ -617,20 +675,32 @@ impl GenerateOtp for UserAuthenticatedDevice { } } -impl AuthenticatedDevice for UserAuthenticatedDevice { - fn new(device: UnauthenticatedDevice, temp_password: Vec<u8>) -> Self { - UserAuthenticatedDevice { +impl<T: Device> AuthenticatedDevice<T> for User<T> { + fn new(device: T, temp_password: Vec<u8>) -> Self { + User { device, temp_password, } } } -impl AdminAuthenticatedDevice { +impl<T: Device + 'static> AsRef<GenerateOtp> for Admin<T> { + fn as_ref(&self) -> &(GenerateOtp + 'static) { + &self.device + } +} + +impl<T: Device + 'static> AsRef<Device> for Admin<T> { + fn as_ref(&self) -> &(Device + 'static) { + &self.device + } +} + +impl<T: Device> Admin<T> { /// Forgets the user authentication and returns an unauthenticated /// device. This method consumes the authenticated device. It does not /// perform any actual commands on the Nitrokey. - pub fn device(self) -> UnauthenticatedDevice { + pub fn device(self) -> T { self.device } @@ -680,9 +750,9 @@ impl AdminAuthenticatedDevice { } } - fn write_otp_slot<T>(&self, data: OtpSlotData, callback: T) -> CommandStatus + fn write_otp_slot<C>(&self, data: OtpSlotData, callback: C) -> CommandStatus where - T: Fn(RawOtpSlotData, *const i8) -> c_int, + C: Fn(RawOtpSlotData, *const i8) -> c_int, { let raw_data = match RawOtpSlotData::new(data) { Ok(raw_data) => raw_data, @@ -694,9 +764,7 @@ impl AdminAuthenticatedDevice { } } -impl Device for AdminAuthenticatedDevice {} - -impl ConfigureOtp for AdminAuthenticatedDevice { +impl<T: Device> ConfigureOtp for Admin<T> { fn write_hotp_slot(&self, data: OtpSlotData, counter: u64) -> CommandStatus { return self.write_otp_slot(data, |raw_data: RawOtpSlotData, temp_password_ptr| unsafe { nitrokey_sys::NK_write_hotp_slot( @@ -740,11 +808,9 @@ impl ConfigureOtp for AdminAuthenticatedDevice { } } -impl GenerateOtp for AdminAuthenticatedDevice {} - -impl AuthenticatedDevice for AdminAuthenticatedDevice { - fn new(device: UnauthenticatedDevice, temp_password: Vec<u8>) -> Self { - AdminAuthenticatedDevice { +impl<T: Device> AuthenticatedDevice<T> for Admin<T> { + fn new(device: T, temp_password: Vec<u8>) -> Self { + Admin { device, temp_password, } |