diff options
| author | Robin Krahl <robin.krahl@ireas.org> | 2018-05-28 22:02:10 +0000 | 
|---|---|---|
| committer | Robin Krahl <robin.krahl@ireas.org> | 2018-05-29 00:03:56 +0200 | 
| commit | 89b8a947e5c622272362e967847eb19337aa68da (patch) | |
| tree | ccbd24b53b4a98312247a304b51774fac2e3b685 /src | |
| parent | f58400a22986cdedf34f85dc17d1d59335ffb404 (diff) | |
| download | nitrokey-rs-89b8a947e5c622272362e967847eb19337aa68da.tar.gz nitrokey-rs-89b8a947e5c622272362e967847eb19337aa68da.tar.bz2 | |
Add rudimentary support for the Nitrokey Storage
This patch adds the Storage struct and the test-storage feature.  It
also enables all currently supported Pro commands for the Storage.
Diffstat (limited to 'src')
| -rw-r--r-- | src/auth.rs | 16 | ||||
| -rw-r--r-- | src/device.rs | 68 | ||||
| -rw-r--r-- | src/lib.rs | 5 | ||||
| -rw-r--r-- | src/tests/device.rs | 51 | ||||
| -rw-r--r-- | src/tests/otp.rs | 29 | ||||
| -rw-r--r-- | src/tests/util.rs | 9 | ||||
| -rw-r--r-- | src/util.rs | 7 | 
7 files changed, 140 insertions, 45 deletions
| diff --git a/src/auth.rs b/src/auth.rs index 80abbc0..c772faf 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,5 +1,5 @@  use config::{Config, RawConfig}; -use device::{Device, DeviceWrapper, Pro}; +use device::{Device, DeviceWrapper, Pro, Storage};  use nitrokey_sys;  use std::ffi::CString;  use std::ops::Deref; @@ -387,3 +387,17 @@ impl Authenticate for Pro {          })      }  } + +impl Authenticate for Storage { +    fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, CommandError)> { +        authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { +            nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr) +        }) +    } + +    fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, CommandError)> { +        authenticate(self, password, |password_ptr, temp_password_ptr| unsafe { +            nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr) +        }) +    } +} diff --git a/src/device.rs b/src/device.rs index 684f64a..1201540 100644 --- a/src/device.rs +++ b/src/device.rs @@ -54,7 +54,7 @@ enum Model {  #[derive(Debug)]  pub enum DeviceWrapper {      /// A Nitrokey Storage device. -    Storage(()), +    Storage(Storage),      /// A Nitrokey Pro device.      Pro(Pro),  } @@ -101,6 +101,48 @@ pub enum DeviceWrapper {  #[derive(Debug)]  pub struct Pro {} +/// A Nitrokey Storage device without user or admin authentication. +/// +/// Use the global function [`connect`][] to obtain an instance wrapper or the method +/// [`connect`][`Storage::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, Storage}; +/// # use nitrokey::CommandError; +/// +/// fn perform_user_task(device: &User<Storage>) {} +/// fn perform_other_task(device: &Storage) {} +/// +/// # fn try_main() -> Result<(), CommandError> { +/// let device = nitrokey::Storage::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 +/// [`Storage::connect`]: #method.connect +#[derive(Debug)] +pub struct Storage {} +  /// A Nitrokey device.  ///  /// This trait provides the commands that can be executed without authentication and that are @@ -368,7 +410,7 @@ fn connect_model(model: Model) -> bool {  impl DeviceWrapper {      fn device(&self) -> &Device {          match *self { -            DeviceWrapper::Storage(_) => panic!("..."), +            DeviceWrapper::Storage(ref storage) => storage,              DeviceWrapper::Pro(ref pro) => pro,          }      } @@ -415,3 +457,25 @@ impl Drop for Pro {  impl Device for Pro {}  impl GenerateOtp for Pro {} + +impl Storage { +    pub fn connect() -> Result<Storage, CommandError> { +        // TODO: maybe Option instead of Result? +        match connect_model(Model::Storage) { +            true => Ok(Storage {}), +            false => Err(CommandError::Unknown), +        } +    } +} + +impl Drop for Storage { +    fn drop(&mut self) { +        unsafe { +            nitrokey_sys::NK_logout(); +        } +    } +} + +impl Device for Storage {} + +impl GenerateOtp for Storage {} @@ -8,7 +8,7 @@  //!  //! 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. +//! [`Pro::connect`][] or [`Storage::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 @@ -76,6 +76,7 @@  //! [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user  //! [`connect`]: fn.connect.html  //! [`Pro::connect`]: struct.Pro.html#fn.connect.html +//! [`Storage::connect`]: struct.Storage.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 @@ -96,7 +97,7 @@ mod util;  mod tests;  pub use config::Config; -pub use device::{connect, Device, DeviceWrapper, Pro}; +pub use device::{connect, Device, DeviceWrapper, Pro, Storage};  pub use auth::{Admin, Authenticate, User};  pub use otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData};  pub use util::{CommandError, CommandStatus, LogLevel}; diff --git a/src/tests/device.rs b/src/tests/device.rs index 394861c..80541d4 100644 --- a/src/tests/device.rs +++ b/src/tests/device.rs @@ -1,6 +1,6 @@  use std::ffi::CStr; -use {Authenticate, CommandError, CommandStatus, Config, Device, Pro}; -use tests::util::{ADMIN_PASSWORD, USER_PASSWORD}; +use {Authenticate, CommandError, CommandStatus, Config, Device}; +use tests::util::{Target, ADMIN_PASSWORD, USER_PASSWORD};  static ADMIN_NEW_PASSWORD: &str = "1234567890";  static USER_NEW_PASSWORD: &str = "abcdefghij"; @@ -10,17 +10,23 @@ static USER_NEW_PASSWORD: &str = "abcdefghij";  fn connect_no_device() {      assert!(::connect().is_err());      assert!(::Pro::connect().is_err()); -    // TODO: test storage -    // assert!(::Storage::connect().is_err()); +    assert!(::Storage::connect().is_err());  }  #[test]  #[cfg_attr(not(feature = "test-pro"), ignore)]  fn connect_pro() {      assert!(::connect().is_ok()); -    assert!(Pro::connect().is_ok()); -    // TODO: test storage -    // assert!(::Storage::connect().is_err()); +    assert!(::Pro::connect().is_ok()); +    assert!(::Storage::connect().is_err()); +} + +#[test] +#[cfg_attr(not(feature = "test-storage"), ignore)] +fn connect_storage() { +    assert!(::connect().is_ok()); +    assert!(::Pro::connect().is_err()); +    assert!(::Storage::connect().is_ok());  }  fn assert_empty_serial_number() { @@ -33,16 +39,16 @@ fn assert_empty_serial_number() {  }  #[test] -#[cfg_attr(not(feature = "test-pro"), ignore)] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]  fn disconnect() { -    ::connect().unwrap(); +    Target::connect().unwrap();      assert_empty_serial_number(); -    ::connect() +    Target::connect()          .unwrap()          .authenticate_admin(ADMIN_PASSWORD)          .unwrap();      assert_empty_serial_number(); -    ::connect() +    Target::connect()          .unwrap()          .authenticate_user(USER_PASSWORD)          .unwrap(); @@ -50,9 +56,9 @@ fn disconnect() {  }  #[test] -#[cfg_attr(not(feature = "test-pro"), ignore)] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]  fn get_serial_number() { -    let device = ::connect().unwrap(); +    let device = Target::connect().unwrap();      let result = device.get_serial_number();      assert!(result.is_ok());      let serial_number = result.unwrap(); @@ -61,8 +67,9 @@ fn get_serial_number() {  }  #[test]  #[cfg_attr(not(feature = "test-pro"), ignore)] +// TODO: adapt for storage  fn get_firmware_version() { -    let device = ::connect().unwrap(); +    let device = Target::connect().unwrap();      assert_eq!(0, device.get_major_firmware_version());      let minor = device.get_minor_firmware_version();      assert!(minor == 7 || minor == 8); @@ -89,9 +96,9 @@ fn user_retry<T: Authenticate + Device>(device: T, suffix: &str, count: u8) -> T  }  #[test] -#[cfg_attr(not(feature = "test-pro"), ignore)] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]  fn get_retry_count() { -    let device = ::connect().unwrap(); +    let device = Target::connect().unwrap();      let device = admin_retry(device, "", 3);      let device = admin_retry(device, "123", 2); @@ -105,9 +112,9 @@ fn get_retry_count() {  }  #[test] -#[cfg_attr(not(feature = "test-pro"), ignore)] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]  fn config() { -    let device = ::connect().unwrap(); +    let device = Target::connect().unwrap();      let admin = device.authenticate_admin(ADMIN_PASSWORD).unwrap();      let config = Config::new(None, None, None, true);      assert_eq!(CommandStatus::Success, admin.write_config(config)); @@ -132,9 +139,9 @@ fn config() {  }  #[test] -#[cfg_attr(not(feature = "test-pro"), ignore)] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]  fn change_user_pin() { -    let device = ::connect().unwrap(); +    let device = Target::connect().unwrap();      let device = device.authenticate_user(USER_PASSWORD).unwrap().device();      let device = device.authenticate_user(USER_NEW_PASSWORD).unwrap_err().0; @@ -158,9 +165,9 @@ fn change_user_pin() {  }  #[test] -#[cfg_attr(not(feature = "test-pro"), ignore)] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]  fn change_admin_pin() { -    let device = ::connect().unwrap(); +    let device = Target::connect().unwrap();      let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device();      let device = device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err().0; diff --git a/src/tests/otp.rs b/src/tests/otp.rs index e96bbfe..10f569d 100644 --- a/src/tests/otp.rs +++ b/src/tests/otp.rs @@ -1,7 +1,7 @@  use std::ops::Deref; -use {Admin, Authenticate, CommandError, CommandStatus, Config, ConfigureOtp, DeviceWrapper, GenerateOtp, +use {Admin, Authenticate, CommandError, CommandStatus, Config, ConfigureOtp, GenerateOtp,       OtpMode, OtpSlotData}; -use tests::util::{ADMIN_PASSWORD, USER_PASSWORD}; +use tests::util::{Target, ADMIN_PASSWORD, USER_PASSWORD};  // test suite according to RFC 4226, Appendix D  static HOTP_SECRET: &str = "3132333435363738393031323334353637383930"; @@ -21,8 +21,8 @@ static TOTP_CODES: &[(u64, &str)] = &[      (20000000000, "65353130"),  ]; -fn get_admin_test_device() -> Admin<DeviceWrapper> { -    ::connect().expect("Could not connect to the Nitrokey Pro.") +fn get_admin_test_device() -> Admin<Target> { +    Target::connect().expect("Could not connect to the Nitrokey Pro.")          .authenticate_admin(ADMIN_PASSWORD)          .expect("Could not login as admin.")  } @@ -40,7 +40,7 @@ fn check_hotp_codes(device: &GenerateOtp) {  }  #[test] -#[cfg_attr(not(feature = "test-pro"), ignore)] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]  fn hotp_no_pin() {      let admin = get_admin_test_device();      let config = Config::new(None, None, None, false); @@ -54,7 +54,7 @@ fn hotp_no_pin() {  }  #[test] -#[cfg_attr(not(feature = "test-pro"), ignore)] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]  fn hotp_pin() {      let admin = get_admin_test_device();      let config = Config::new(None, None, None, true); @@ -68,7 +68,7 @@ fn hotp_pin() {  }  #[test] -#[cfg_attr(not(feature = "test-pro"), ignore)] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]  fn hotp_slot_name() {      let admin = get_admin_test_device();      let slot_data = OtpSlotData::new(1, "test-hotp", HOTP_SECRET, OtpMode::SixDigits); @@ -82,7 +82,7 @@ fn hotp_slot_name() {  }  #[test] -#[cfg_attr(not(feature = "test-pro"), ignore)] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]  fn hotp_error() {      let admin = get_admin_test_device();      let slot_data = OtpSlotData::new(1, "", HOTP_SECRET, OtpMode::SixDigits); @@ -100,7 +100,7 @@ fn hotp_error() {  }  #[test] -#[cfg_attr(not(feature = "test-pro"), ignore)] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]  fn hotp_erase() {      let admin = get_admin_test_device();      let config = Config::new(None, None, None, false); @@ -141,7 +141,7 @@ fn check_totp_codes(device: &GenerateOtp) {  }  #[test] -#[cfg_attr(not(feature = "test-pro"), ignore)] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]  fn totp_no_pin() {      // TODO: this test may fail due to bad timing --> find solution      let admin = get_admin_test_device(); @@ -156,7 +156,7 @@ fn totp_no_pin() {  }  #[test] -#[cfg_attr(not(feature = "test-pro"), ignore)] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]  fn totp_pin() {      // TODO: this test may fail due to bad timing --> find solution      let admin = get_admin_test_device(); @@ -171,7 +171,7 @@ fn totp_pin() {  }  #[test] -#[cfg_attr(not(feature = "test-pro"), ignore)] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]  fn totp_slot_name() {      let admin = get_admin_test_device();      let slot_data = OtpSlotData::new(1, "test-totp", TOTP_SECRET, OtpMode::EightDigits); @@ -186,7 +186,7 @@ fn totp_slot_name() {  }  #[test] -#[cfg_attr(not(feature = "test-pro"), ignore)] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]  fn totp_error() {      let admin = get_admin_test_device();      let slot_data = OtpSlotData::new(1, "", HOTP_SECRET, OtpMode::SixDigits); @@ -204,7 +204,7 @@ fn totp_error() {  }  #[test] -#[cfg_attr(not(feature = "test-pro"), ignore)] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]  fn totp_erase() {      let admin = get_admin_test_device();      let config = Config::new(None, None, None, false); @@ -224,4 +224,3 @@ fn totp_erase() {      assert_eq!("test2", device.get_totp_slot_name(2).unwrap());  } - diff --git a/src/tests/util.rs b/src/tests/util.rs index cbf6b93..c6fbb8f 100644 --- a/src/tests/util.rs +++ b/src/tests/util.rs @@ -1,2 +1,11 @@  pub static ADMIN_PASSWORD: &str = "12345678";  pub static USER_PASSWORD: &str = "123456"; + +#[cfg(feature = "test-no-device")] +pub type Target = ::Pro; + +#[cfg(feature = "test-pro")] +pub type Target = ::Pro; + +#[cfg(feature = "test-storage")] +pub type Target = ::Storage; diff --git a/src/util.rs b/src/util.rs index 22cccb3..364b0de 100644 --- a/src/util.rs +++ b/src/util.rs @@ -48,9 +48,10 @@ pub enum CommandStatus {      Error(CommandError),  } -/// Log level for libnitrokey.  Setting the log level to a lower level enables all output from -/// higher levels too.  Currently, only the log levels `Warning`, `DebugL1`, `Debug` and `DebugL2` -/// are actually used. +/// Log level for libnitrokey. +/// +/// Setting the log level to a lower level enables all output from higher levels too.  Currently, +/// only the log levels `Warning`, `DebugL1`, `Debug` and `DebugL2` are actually used.  #[derive(Debug, PartialEq)]  pub enum LogLevel {      /// Error messages.  Currently not used. | 
