diff options
-rw-r--r-- | nitrocli/CHANGELOG.md | 1 | ||||
-rw-r--r-- | nitrocli/Cargo.lock | 4 | ||||
-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 |
13 files changed, 246 insertions, 35 deletions
diff --git a/nitrocli/CHANGELOG.md b/nitrocli/CHANGELOG.md index ef0e1f7..4ee73a2 100644 --- a/nitrocli/CHANGELOG.md +++ b/nitrocli/CHANGELOG.md @@ -3,6 +3,7 @@ Unreleased - Store cached PINs on a per-device basis to better support multi-device scenarios - Further decreased binary size by using system allocator +- Bumped `nitrokey` dependency to `0.3.2` - Bumped `nitrokey-sys` dependency to `3.4.3` diff --git a/nitrocli/Cargo.lock b/nitrocli/Cargo.lock index a14111c..c357ab1 100644 --- a/nitrocli/Cargo.lock +++ b/nitrocli/Cargo.lock @@ -76,14 +76,14 @@ dependencies = [ "argparse 0.2.2", "base32 0.4.0", "libc 0.2.45", - "nitrokey 0.3.1", + "nitrokey 0.3.2", "nitrokey-test 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "nitrokey" -version = "0.3.1" +version = "0.3.2" dependencies = [ "libc 0.2.45", "nitrokey-sys 3.4.3", 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"; |