aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--nitrocli/CHANGELOG.md1
-rw-r--r--nitrocli/Cargo.lock4
-rw-r--r--nitrokey/CHANGELOG.md9
-rw-r--r--nitrokey/Cargo.toml2
-rw-r--r--nitrokey/README.md14
-rw-r--r--nitrokey/TODO.md13
-rw-r--r--nitrokey/src/device.rs91
-rw-r--r--nitrokey/src/lib.rs43
-rw-r--r--nitrokey/src/util.rs14
-rw-r--r--nitrokey/tests/device.rs61
-rw-r--r--nitrokey/tests/lib.rs8
-rw-r--r--nitrokey/tests/otp.rs20
-rw-r--r--nitrokey/tests/util/mod.rs1
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";