diff options
Diffstat (limited to 'nitrokey')
| -rw-r--r-- | nitrokey/CHANGELOG.md | 9 | ||||
| -rw-r--r-- | nitrokey/Cargo.toml | 2 | ||||
| -rw-r--r-- | nitrokey/README.md | 14 | ||||
| -rw-r--r-- | nitrokey/TODO.md | 13 | ||||
| -rw-r--r-- | nitrokey/src/device.rs | 91 | ||||
| -rw-r--r-- | nitrokey/src/lib.rs | 43 | ||||
| -rw-r--r-- | nitrokey/src/util.rs | 14 | ||||
| -rw-r--r-- | nitrokey/tests/device.rs | 61 | ||||
| -rw-r--r-- | nitrokey/tests/lib.rs | 8 | ||||
| -rw-r--r-- | nitrokey/tests/otp.rs | 20 | ||||
| -rw-r--r-- | nitrokey/tests/util/mod.rs | 1 | 
11 files changed, 243 insertions, 33 deletions
| diff --git a/nitrokey/CHANGELOG.md b/nitrokey/CHANGELOG.md index f79111e..72e6986 100644 --- a/nitrokey/CHANGELOG.md +++ b/nitrokey/CHANGELOG.md @@ -1,3 +1,12 @@ +# v0.3.2 (2019-01-12) +- Make three additional error codes known: `CommandError::StringTooLong`, +  `CommandError::InvalidHexString` and `CommandError::TargetBufferTooSmall`. +- Add the `get_library_version` function to query the libnitrokey version. +- Add the `wink` method to the `Storage` struct. +- Add the `set_unencrypted_volume_mode` to set the access mode of the +  unencrypted volume. +- Add the `export_firmware` method to the `Storage` struct. +  # v0.3.1 (2019-01-07)  - Use `nitrokey-test` to select and execute the unit tests.  - Add support for the hidden volumes on a Nitrokey Storage diff --git a/nitrokey/Cargo.toml b/nitrokey/Cargo.toml index f7c1baf..09811f0 100644 --- a/nitrokey/Cargo.toml +++ b/nitrokey/Cargo.toml @@ -1,6 +1,6 @@  [package]  name = "nitrokey" -version = "0.3.1" +version = "0.3.2"  authors = ["Robin Krahl <robin.krahl@ireas.org>"]  edition = "2018"  homepage = "https://code.ireas.org/nitrokey-rs/" diff --git a/nitrokey/README.md b/nitrokey/README.md index 53054de..567ae58 100644 --- a/nitrokey/README.md +++ b/nitrokey/README.md @@ -21,13 +21,21 @@ available but still under development.  The following functions provided by `libnitrokey` are deliberately not  supported by `nitrokey-rs`: -- `NK_get_time()`.  This method is useless as it will always cause a timestamp +- `NK_get_device_model`.  We know which model we connected to, so we can +  provide this information without calling `libnitrokey`. +- `NK_get_time`.  This method is useless as it will always cause a timestamp    error on the device (see [pull request #114][] for `libnitrokey` for details). -- `NK_get_status()`.  This method only provides a string representation of +- `NK_get_status`.  This method only provides a string representation of    data that can be accessed by other methods (firmware version, serial number,    configuration). -- `NK_get_status_storage_as_string()`.  This method only provides an incomplete +- `NK_get_status_storage_as_string`.  This method only provides an incomplete    string representation of the data returned by `NK_get_status_storage`. +- `NK_set_unencrypted_volume_rorw_pin_type_user`, +  `NK_set_unencrypted_read_only`, `NK_set_unencrypted_read_write`, +  `NK_set_encrypted_read_only` and `NK_set_encrypted_read_write`.  These +  methods are only relevant for older firmware versions (pre-v0.51).  As the +  Nitrokey Storage firmware can be updated easily, we do not support these +  outdated versions.  ## Tests diff --git a/nitrokey/TODO.md b/nitrokey/TODO.md index f839dc3..7c8c5e6 100644 --- a/nitrokey/TODO.md +++ b/nitrokey/TODO.md @@ -1,26 +1,13 @@  - Add support for the currently unsupported commands: -    - `NK_set_unencrypted_volume_rorw_pin_type_user`      - `NK_is_AES_supported`      - `NK_send_startup` -    - `NK_set_unencrypted_read_only` -    - `NK_set_unencrypted_read_only_admin` -    - `NK_set_unencrypted_read_write` -    - `NK_set_unencrypted_read_write_admin` -    - `NK_set_encrypted_read_only` -    - `NK_set_encrypted_read_write` -    - `NK_export_firmware`      - `NK_clear_new_sd_card_warning`      - `NK_fill_SD_card_with_random_data`      - `NK_get_SD_usage_data_as_string`      - `NK_get_progress_bar_value`      - `NK_list_devices_by_cpuID`      - `NK_connect_with_ID` -    - `NK_get_device_model` -    - `NK_get_library_version` -    - `NK_get_major_library_version` -    - `NK_get_minor_libray_version`      - `NK_get_storage_production_info` -    - `NK_wink`  - Fix timing issues with the `totp_no_pin` and `totp_pin` test cases.  - Clear passwords from memory.  - Find a nicer syntax for the `write_config` test. diff --git a/nitrokey/src/device.rs b/nitrokey/src/device.rs index 2eee08e..f247f58 100644 --- a/nitrokey/src/device.rs +++ b/nitrokey/src/device.rs @@ -33,6 +33,24 @@ impl fmt::Display for Model {      }  } +/// The access mode of a volume on the Nitrokey Storage. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum VolumeMode { +    /// A read-only volume. +    ReadOnly, +    /// A read-write volume. +    ReadWrite, +} + +impl fmt::Display for VolumeMode { +    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +        match *self { +            VolumeMode::ReadOnly => f.write_str("read-only"), +            VolumeMode::ReadWrite => f.write_str("read-write"), +        } +    } +} +  /// A wrapper for a Nitrokey device of unknown type.  ///  /// Use the function [`connect`][] to obtain a wrapped instance.  The wrapper implements all traits @@ -89,7 +107,6 @@ impl fmt::Display for Model {  /// ```  ///  /// [`connect`]: fn.connect.html -// TODO: add example for Storage-specific code  #[derive(Debug)]  pub enum DeviceWrapper {      /// A Nitrokey Storage device. @@ -1059,6 +1076,52 @@ impl Storage {          }      } +    /// Sets the access mode of the unencrypted volume. +    /// +    /// This command will reconnect the unencrypted volume so buffers should be flushed before +    /// calling it.  Since firmware version v0.51, this command requires the admin PIN.  Older +    /// firmware versions are not supported. +    /// +    /// # Errors +    /// +    /// - [`InvalidString`][] if the provided password contains a null byte +    /// - [`WrongPassword`][] if the provided admin password is wrong +    /// +    /// # Example +    /// +    /// ```no_run +    /// # use nitrokey::CommandError; +    /// use nitrokey::VolumeMode; +    /// +    /// # fn try_main() -> Result<(), CommandError> { +    /// let device = nitrokey::Storage::connect()?; +    /// match device.set_unencrypted_volume_mode("123456", VolumeMode::ReadWrite) { +    ///     Ok(()) => println!("Set the unencrypted volume to read-write mode."), +    ///     Err(err) => println!("Could not set the unencrypted volume to read-write mode: {}", err), +    /// }; +    /// #     Ok(()) +    /// # } +    /// ``` +    /// +    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString +    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword +    pub fn set_unencrypted_volume_mode( +        &self, +        admin_pin: &str, +        mode: VolumeMode, +    ) -> Result<(), CommandError> { +        let admin_pin = get_cstring(admin_pin)?; +        let result = match mode { +            VolumeMode::ReadOnly => unsafe { +                nitrokey_sys::NK_set_unencrypted_read_only_admin(admin_pin.as_ptr()) +            }, +            VolumeMode::ReadWrite => unsafe { +                nitrokey_sys::NK_set_unencrypted_read_write_admin(admin_pin.as_ptr()) +            }, +        }; +        get_command_result(result) +    } +      /// Returns the status of the connected storage device.      ///      /// # Example @@ -1102,6 +1165,32 @@ impl Storage {          let result = get_command_result(raw_result);          result.and(Ok(StorageStatus::from(raw_status)))      } + +    /// Blinks the red and green LED alternatively and infinitely until the device is reconnected. +    pub fn wink(&self) -> Result<(), CommandError> { +        get_command_result(unsafe { nitrokey_sys::NK_wink() }) +    } + +    /// Exports the firmware to the unencrypted volume. +    /// +    /// This command requires the admin PIN.  The unencrypted volume must be in read-write mode +    /// when this command is executed.  Otherwise, it will still return `Ok` but not write the +    /// firmware. +    /// +    /// This command unmounts the unencrypted volume if it has been mounted, so all buffers should +    /// be flushed.  The firmware is written to the `firmware.bin` file on the unencrypted volume. +    /// +    /// # Errors +    /// +    /// - [`InvalidString`][] if one of the provided passwords contains a null byte +    /// - [`WrongPassword`][] if the admin password is wrong +    /// +    /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString +    /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword +    pub fn export_firmware(&self, admin_pin: &str) -> Result<(), CommandError> { +        let admin_pin_string = get_cstring(admin_pin)?; +        get_command_result(unsafe { nitrokey_sys::NK_export_firmware(admin_pin_string.as_ptr()) }) +    }  }  impl Drop for Storage { diff --git a/nitrokey/src/lib.rs b/nitrokey/src/lib.rs index bb34870..c50b713 100644 --- a/nitrokey/src/lib.rs +++ b/nitrokey/src/lib.rs @@ -98,12 +98,32 @@ use nitrokey_sys;  pub use crate::auth::{Admin, Authenticate, User};  pub use crate::config::Config;  pub use crate::device::{ -    connect, connect_model, Device, DeviceWrapper, Model, Pro, Storage, StorageStatus, VolumeStatus, +    connect, connect_model, Device, DeviceWrapper, Model, Pro, Storage, StorageStatus, VolumeMode, +    VolumeStatus,  };  pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData};  pub use crate::pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT};  pub use crate::util::{CommandError, LogLevel}; +/// A version of the libnitrokey library. +/// +/// Use the [`get_library_version`](fn.get_library_version.html) function to query the library +/// version. +#[derive(Clone, Debug, PartialEq)] +pub struct Version { +    /// The library version as a string. +    /// +    /// The library version is the output of `git describe --always` at compile time, for example +    /// `v3.3` or `v3.4.1`.  If the library has not been built from a release, the version string +    /// contains the number of commits since the last release and the hash of the current commit, for +    /// example `v3.3-19-gaee920b`. +    pub git: String, +    /// The major library version. +    pub major: u32, +    /// The minor library version. +    pub minor: u32, +} +  /// 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`][]). @@ -125,3 +145,24 @@ pub fn set_log_level(level: LogLevel) {          nitrokey_sys::NK_set_debug_level(level.into());      }  } + +/// Returns the libnitrokey library version. +/// +/// # Example +/// +/// ``` +/// let version = nitrokey::get_library_version(); +/// println!("Using libnitrokey {}", version.git); +/// ``` +pub fn get_library_version() -> Version { +    // NK_get_library_version returns a static string, so we don’t have to free the pointer. +    let git = unsafe { nitrokey_sys::NK_get_library_version() }; +    let git = if git.is_null() { +        String::new() +    } else { +        util::owned_str_from_ptr(git) +    }; +    let major = unsafe { nitrokey_sys::NK_get_major_library_version() }; +    let minor = unsafe { nitrokey_sys::NK_get_minor_library_version() }; +    Version { git, major, minor } +} diff --git a/nitrokey/src/util.rs b/nitrokey/src/util.rs index 1ecc0b7..cb109d0 100644 --- a/nitrokey/src/util.rs +++ b/nitrokey/src/util.rs @@ -36,8 +36,14 @@ pub enum CommandError {      Undefined,      /// You passed a string containing a null byte.      InvalidString, +    /// A supplied string exceeded a length limit. +    StringTooLong,      /// You passed an invalid slot.      InvalidSlot, +    /// The supplied string was not in hexadecimal format. +    InvalidHexString, +    /// The target buffer was smaller than the source. +    TargetBufferTooSmall,  }  /// Log level for libnitrokey. @@ -134,7 +140,12 @@ impl CommandError {              }              CommandError::Undefined => "An unspecified error occurred".into(),              CommandError::InvalidString => "You passed a string containing a null byte".into(), +            CommandError::StringTooLong => "The supplied string is too long".into(),              CommandError::InvalidSlot => "The given slot is invalid".into(), +            CommandError::InvalidHexString => { +                "The supplied string is not in hexadecimal format".into() +            } +            CommandError::TargetBufferTooSmall => "The target buffer is too small".into(),          }      }  } @@ -158,7 +169,10 @@ impl From<c_int> for CommandError {              8 => CommandError::NotSupported,              9 => CommandError::UnknownCommand,              10 => CommandError::AesDecryptionFailed, +            200 => CommandError::StringTooLong,              201 => CommandError::InvalidSlot, +            202 => CommandError::InvalidHexString, +            203 => CommandError::TargetBufferTooSmall,              x => CommandError::Unknown(x.into()),          }      } diff --git a/nitrokey/tests/device.rs b/nitrokey/tests/device.rs index db8194c..e40ae12 100644 --- a/nitrokey/tests/device.rs +++ b/nitrokey/tests/device.rs @@ -6,13 +6,14 @@ use std::{thread, time};  use nitrokey::{      Authenticate, CommandError, Config, ConfigureOtp, Device, GenerateOtp, GetPasswordSafe, -    OtpMode, OtpSlotData, +    OtpMode, OtpSlotData, Storage, VolumeMode,  };  use nitrokey_test::test as test_device; -use crate::util::{ADMIN_PASSWORD, UPDATE_PIN, USER_PASSWORD}; +use crate::util::{ADMIN_PASSWORD, USER_PASSWORD};  static ADMIN_NEW_PASSWORD: &str = "1234567890"; +static UPDATE_PIN: &str = "12345678";  static UPDATE_NEW_PIN: &str = "87654321";  static USER_NEW_PASSWORD: &str = "abcdefghij"; @@ -45,9 +46,6 @@ fn connect_pro(device: Pro) {      assert!(nitrokey::connect().is_ok());      assert!(nitrokey::connect_model(nitrokey::Model::Pro).is_ok());      assert!(nitrokey::Pro::connect().is_ok()); - -    assert!(nitrokey::connect_model(nitrokey::Model::Storage).is_err()); -    assert!(nitrokey::Storage::connect().is_err());  }  #[test_device] @@ -58,9 +56,6 @@ fn connect_storage(device: Storage) {      assert!(nitrokey::connect().is_ok());      assert!(nitrokey::connect_model(nitrokey::Model::Storage).is_ok());      assert!(nitrokey::Storage::connect().is_ok()); - -    assert!(nitrokey::connect_model(nitrokey::Model::Pro).is_err()); -    assert!(nitrokey::Pro::connect().is_err());  }  fn assert_empty_serial_number() { @@ -404,9 +399,59 @@ fn lock(device: Storage) {  }  #[test_device] +fn set_unencrypted_volume_mode(device: Storage) { +    fn assert_mode(device: &Storage, mode: VolumeMode) { +        let status = device.get_status(); +        assert!(status.is_ok()); +        assert_eq!( +            status.unwrap().unencrypted_volume.read_only, +            mode == VolumeMode::ReadOnly +        ); +    } + +    fn assert_success(device: &Storage, mode: VolumeMode) { +        assert_eq!( +            Ok(()), +            device.set_unencrypted_volume_mode(ADMIN_PASSWORD, mode) +        ); +        assert_mode(&device, mode); +    } + +    assert_success(&device, VolumeMode::ReadOnly); + +    assert_eq!( +        Err(CommandError::WrongPassword), +        device.set_unencrypted_volume_mode(USER_PASSWORD, VolumeMode::ReadOnly) +    ); +    assert_mode(&device, VolumeMode::ReadOnly); + +    assert_success(&device, VolumeMode::ReadWrite); +    assert_success(&device, VolumeMode::ReadWrite); +    assert_success(&device, VolumeMode::ReadOnly); +} + +#[test_device]  fn get_storage_status(device: Storage) {      let status = device.get_status().unwrap();      assert!(status.serial_number_sd_card > 0);      assert!(status.serial_number_smart_card > 0);  } + +#[test_device] +fn export_firmware(device: Storage) { +    assert_eq!( +        Err(CommandError::WrongPassword), +        device.export_firmware("someadminpn") +    ); +    assert_eq!(Ok(()), device.export_firmware(ADMIN_PASSWORD)); +    assert_eq!( +        Ok(()), +        device.set_unencrypted_volume_mode(ADMIN_PASSWORD, VolumeMode::ReadWrite) +    ); +    assert_eq!(Ok(()), device.export_firmware(ADMIN_PASSWORD)); +    assert_eq!( +        Ok(()), +        device.set_unencrypted_volume_mode(ADMIN_PASSWORD, VolumeMode::ReadOnly) +    ); +} diff --git a/nitrokey/tests/lib.rs b/nitrokey/tests/lib.rs new file mode 100644 index 0000000..06de0ad --- /dev/null +++ b/nitrokey/tests/lib.rs @@ -0,0 +1,8 @@ +#[test] +fn get_library_version() { +    let version = nitrokey::get_library_version(); + +    assert!(!version.git.is_empty()); +    assert!(version.git.starts_with("v")); +    assert!(version.major > 0); +} diff --git a/nitrokey/tests/otp.rs b/nitrokey/tests/otp.rs index 2b46088..712f7a2 100644 --- a/nitrokey/tests/otp.rs +++ b/nitrokey/tests/otp.rs @@ -125,6 +125,11 @@ fn hotp_error(device: DeviceWrapper) {          Err(CommandError::InvalidSlot),          admin.write_hotp_slot(slot_data, 0)      ); +    let slot_data = OtpSlotData::new(1, "test", "foobar", OtpMode::SixDigits); +    assert_eq!( +        Err(CommandError::InvalidHexString), +        admin.write_hotp_slot(slot_data, 0) +    );      let code = admin.get_hotp_code(4);      assert_eq!(CommandError::InvalidSlot, code.unwrap_err());  } @@ -256,17 +261,22 @@ fn totp_slot_name(device: DeviceWrapper) {  #[test_device]  fn totp_error(device: DeviceWrapper) {      let admin = make_admin_test_device(device); -    let slot_data = OtpSlotData::new(1, "", HOTP_SECRET, OtpMode::SixDigits); +    let slot_data = OtpSlotData::new(1, "", TOTP_SECRET, OtpMode::SixDigits);      assert_eq!(          Err(CommandError::NoName), -        admin.write_hotp_slot(slot_data, 0) +        admin.write_totp_slot(slot_data, 0)      ); -    let slot_data = OtpSlotData::new(4, "test", HOTP_SECRET, OtpMode::SixDigits); +    let slot_data = OtpSlotData::new(20, "test", TOTP_SECRET, OtpMode::SixDigits);      assert_eq!(          Err(CommandError::InvalidSlot), -        admin.write_hotp_slot(slot_data, 0) +        admin.write_totp_slot(slot_data, 0)      ); -    let code = admin.get_hotp_code(4); +    let slot_data = OtpSlotData::new(4, "test", "foobar", OtpMode::SixDigits); +    assert_eq!( +        Err(CommandError::InvalidHexString), +        admin.write_totp_slot(slot_data, 0) +    ); +    let code = admin.get_totp_code(20);      assert_eq!(CommandError::InvalidSlot, code.unwrap_err());  } diff --git a/nitrokey/tests/util/mod.rs b/nitrokey/tests/util/mod.rs index 1e522fd..cbf6b93 100644 --- a/nitrokey/tests/util/mod.rs +++ b/nitrokey/tests/util/mod.rs @@ -1,3 +1,2 @@  pub static ADMIN_PASSWORD: &str = "12345678"; -pub static UPDATE_PIN: &str = "12345678";  pub static USER_PASSWORD: &str = "123456"; | 
