From e0baabd1046087a9f545d9ef65539c23eb91e85f Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 6 Jan 2020 11:12:50 +0000 Subject: Update the list of unsupported functions After some more research, I decided not to implement some of the methods provided by libnitrokey. This patch adds them to the list of unsupported functions in the readme, and removes them from the list of unimplemented functions in the todo list. --- CHANGELOG.md | 8 ++++++++ README.md | 13 ++++++++++--- TODO.md | 8 ++------ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4451bc..71d2377 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ Copyright (C) 2019-2020 Robin Krahl SPDX-License-Identifier: CC0-1.0 --> +# Unreleased +- List these libnitrokey functions as unsupported: + - `NK_change_firmware_password_pro` + - `NK_connect_with_ID` + - `NK_enable_firmware_update_pro` + - `NK_list_devices_by_cpuID` + - `NK_send_startup` + # v0.4.0 (2020-01-02) - Remove the `test-pro` and `test-storage` features. - Implement `Display` for `Version`. diff --git a/README.md b/README.md index 12a9f6d..d62e38e 100644 --- a/README.md +++ b/README.md @@ -26,13 +26,20 @@ available but still under development. The following functions provided by `libnitrokey` are deliberately not supported by `nitrokey-rs`: +- `NK_connect_with_ID`, `NK_list_devices_by_cpuID`. These functions can be + replaced by calls to `NK_connect_with_path` and `NK_list_devices`, which + also have a cleaner API. +- `NK_enable_firmware_update_pro`, `NK_change_firmware_password_pro`. These + functions execute commands that are not yet supported by the Nitrokey Pro + firmware. - `NK_get_device_model`. We know which model we connected to, so we can provide this information without calling `libnitrokey`. -- `NK_is_AES_supported`. This method is no longer needed for Nitrokey devices - with a recent firmware version. +- `NK_is_AES_supported`. This function is no longer needed for Nitrokey + devices with a recent firmware version. +- `NK_send_startup`. Currently, this function is redundant to `NK_get_time`. - `NK_set_unencrypted_volume_rorw_pin_type_user`, `NK_set_unencrypted_read_only`, `NK_set_unencrypted_read_write`. These - methods are only relevant for older firmware versions (pre-v0.51). As the + functions 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. - `NK_totp_get_time`, `NK_status`. These functions are deprecated. diff --git a/TODO.md b/TODO.md index 54525ef..8adea87 100644 --- a/TODO.md +++ b/TODO.md @@ -4,18 +4,13 @@ SPDX-License-Identifier: CC0-1.0 --> - Add support for the currently unsupported commands: - - `NK_send_startup` - `NK_fill_SD_card_with_random_data` - `NK_get_SD_usage_data` - `NK_get_progress_bar_value` - - `NK_list_devices_by_cpuID` - - `NK_connect_with_ID` - - `NK_get_status` + - `NK_get_status` -- waiting for [libnitrokey issue 166][] - `NK_list_devices` - `NK_free_device_info` - `NK_connect_with_path` - - `NK_enable_firmware_update_pro` - - `NK_change_firmware_password_pro` - Clear passwords from memory. - Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware issue 65][]). @@ -23,4 +18,5 @@ SPDX-License-Identifier: CC0-1.0 - Check timing in Storage tests. - Consider restructuring `device::StorageStatus`. +[libnitrokey issue 166]: https://github.com/Nitrokey/libnitrokey/issues/166 [nitrokey-storage-firmware issue 65]: https://github.com/Nitrokey/nitrokey-storage-firmware/issues/65 -- cgit v1.2.3 From ddb175bd963070e837aa9207c2dee32ffffc51f4 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 30 Dec 2019 10:53:54 +0100 Subject: Add the Error::UnsupportedModelError variant This patch adds the UnsupportedModelError variant to the Error enum: When parsing the model returned by libnitrokey, we should provide a meaningful error message for unknown values. --- CHANGELOG.md | 2 ++ src/error.rs | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71d2377..73bf782 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ SPDX-License-Identifier: CC0-1.0 - `NK_enable_firmware_update_pro` - `NK_list_devices_by_cpuID` - `NK_send_startup` +- Implement connection by path: + - Add the `Error::UnsupportedDeviceError` variant. # v0.4.0 (2020-01-02) - Remove the `test-pro` and `test-storage` features. diff --git a/src/error.rs b/src/error.rs index 9e6adc0..f9af594 100644 --- a/src/error.rs +++ b/src/error.rs @@ -28,6 +28,8 @@ pub enum Error { UnexpectedError, /// An unknown error returned by libnitrokey. UnknownError(i64), + /// An error caused by a Nitrokey model that is not supported by this crate. + UnsupportedModelError, /// An error occurred when interpreting a UTF-8 string. Utf8Error(str::Utf8Error), } @@ -102,6 +104,7 @@ impl error::Error for Error { Error::RandError(ref err) => Some(err.as_ref()), Error::UnexpectedError => None, Error::UnknownError(_) => None, + Error::UnsupportedModelError => None, Error::Utf8Error(ref err) => Some(err), } } @@ -118,6 +121,7 @@ impl fmt::Display for Error { Error::RandError(ref err) => write!(f, "RNG error: {}", err), Error::UnexpectedError => write!(f, "An unexpected error occurred"), Error::UnknownError(ref err) => write!(f, "Unknown error: {}", err), + Error::UnsupportedModelError => write!(f, "Unsupported Nitrokey model"), Error::Utf8Error(ref err) => write!(f, "UTF-8 error: {}", err), } } -- cgit v1.2.3 From 6b439950c3c856f1b0eb6df2c9e89387b1697608 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sat, 28 Dec 2019 23:34:58 +0100 Subject: Implement conversion traits for Model and NK_device_model A nitrokey_sys::NK_device_model (= u32) value may correspond to a nitrokey::Model, and vice versa. This patch adds the appropriate From and TryFrom implementations. --- src/device/mod.rs | 44 +++++++++++++++++++++++++++++--------------- src/lib.rs | 2 ++ 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/device/mod.rs b/src/device/mod.rs index 5e15f08..a3a0255 100644 --- a/src/device/mod.rs +++ b/src/device/mod.rs @@ -5,6 +5,7 @@ mod pro; mod storage; mod wrapper; +use std::convert::TryFrom; use std::fmt; use libc; @@ -43,6 +44,30 @@ impl fmt::Display for Model { } } +impl From for nitrokey_sys::NK_device_model { + fn from(model: Model) -> Self { + match model { + Model::Storage => nitrokey_sys::NK_device_model_NK_STORAGE, + Model::Pro => nitrokey_sys::NK_device_model_NK_PRO, + } + } +} + +impl TryFrom for Model { + type Error = Error; + + fn try_from(model: nitrokey_sys::NK_device_model) -> Result { + match model { + nitrokey_sys::NK_device_model_NK_DISCONNECTED => { + Err(CommunicationError::NotConnected.into()) + } + nitrokey_sys::NK_device_model_NK_PRO => Ok(Model::Pro), + nitrokey_sys::NK_device_model_NK_STORAGE => Ok(Model::Storage), + _ => Err(Error::UnsupportedModelError), + } + } +} + /// A firmware version for a Nitrokey device. #[derive(Clone, Copy, Debug, PartialEq)] pub struct FirmwareVersion { @@ -428,12 +453,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt } } -fn get_connected_model() -> Option { - match unsafe { nitrokey_sys::NK_get_device_model() } { - nitrokey_sys::NK_device_model_NK_PRO => Some(Model::Pro), - nitrokey_sys::NK_device_model_NK_STORAGE => Some(Model::Storage), - _ => None, - } +fn get_connected_model() -> Result { + Model::try_from(unsafe { nitrokey_sys::NK_get_device_model() }) } pub(crate) fn create_device_wrapper( @@ -449,16 +470,9 @@ pub(crate) fn create_device_wrapper( pub(crate) fn get_connected_device( manager: &mut crate::Manager, ) -> Result, Error> { - match get_connected_model() { - Some(model) => Ok(create_device_wrapper(manager, model)), - None => Err(CommunicationError::NotConnected.into()), - } + Ok(create_device_wrapper(manager, get_connected_model()?)) } pub(crate) fn connect_enum(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 } + unsafe { nitrokey_sys::NK_login_enum(model.into()) == 1 } } diff --git a/src/lib.rs b/src/lib.rs index 059792d..0eb4f40 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -235,6 +235,7 @@ impl Manager { /// # Errors /// /// - [`NotConnected`][] if no Nitrokey device is connected + /// - [`UnsupportedModelError`][] if the Nitrokey device is not supported by this crate /// /// # Example /// @@ -252,6 +253,7 @@ impl Manager { /// ``` /// /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected + /// [`UnsupportedModelError`]: enum.Error.html#variant.UnsupportedModelError pub fn connect(&mut self) -> Result, Error> { if unsafe { nitrokey_sys::NK_login_auto() } == 1 { device::get_connected_device(self) -- cgit v1.2.3 From bcad061ed3e7777547c1b6fc9223dd65f752d94e Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sat, 28 Dec 2019 23:53:24 +0100 Subject: Add the DeviceInfo struct In the next patch, we will add support for the NK_list_devices functions that returns a list of NK_device_info structs with information about the connected devices. This patch introduces the DeviceInfo struct that holds the information returned by NK_list_devices and that can be created from a NK_device_info struct. --- CHANGELOG.md | 1 + src/device/mod.rs | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 4 +-- 3 files changed, 106 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73bf782..3798540 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ SPDX-License-Identifier: CC0-1.0 - `NK_send_startup` - Implement connection by path: - Add the `Error::UnsupportedDeviceError` variant. + - Add the `DeviceInfo` struct. # v0.4.0 (2020-01-02) - Remove the `test-pro` and `test-storage` features. diff --git a/src/device/mod.rs b/src/device/mod.rs index a3a0255..e015886 100644 --- a/src/device/mod.rs +++ b/src/device/mod.rs @@ -5,7 +5,9 @@ mod pro; mod storage; mod wrapper; -use std::convert::TryFrom; +use std::cmp; +use std::convert::{TryFrom, TryInto}; +use std::ffi; use std::fmt; use libc; @@ -17,7 +19,8 @@ use crate::error::{CommunicationError, Error}; use crate::otp::GenerateOtp; use crate::pws::GetPasswordSafe; use crate::util::{ - get_command_result, get_cstring, get_last_error, result_from_string, result_or_error, + get_command_result, get_cstring, get_last_error, owned_str_from_ptr, result_from_string, + result_or_error, }; pub use pro::Pro; @@ -68,6 +71,73 @@ impl TryFrom for Model { } } +/// Connection information for a Nitrokey device. +#[derive(Clone, Debug, PartialEq)] +pub struct DeviceInfo { + /// The model of the Nitrokey device, or `None` if the model is not supported by this crate. + pub model: Option, + /// The USB device path. + pub path: String, + /// The serial number as a 8-character hex string, or `None` if the device does not expose its + /// serial number. + pub serial_number: Option, +} + +impl TryFrom<&nitrokey_sys::NK_device_info> for DeviceInfo { + type Error = Error; + + fn try_from(device_info: &nitrokey_sys::NK_device_info) -> Result { + let model_result = device_info.model.try_into(); + let model_option = model_result.map(Some).or_else(|err| match err { + Error::UnsupportedModelError => Ok(None), + _ => Err(err), + })?; + let serial_number = unsafe { ffi::CStr::from_ptr(device_info.serial_number) } + .to_str() + .map_err(Error::from)?; + Ok(DeviceInfo { + model: model_option, + path: owned_str_from_ptr(device_info.path)?, + serial_number: get_hidapi_serial_number(serial_number), + }) + } +} + +impl fmt::Display for DeviceInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.model { + Some(model) => write!(f, "Nitrokey {}", model)?, + None => write!(f, "Unsupported Nitrokey model")?, + } + write!(f, " at {} with ", self.path)?; + match &self.serial_number { + Some(ref serial_number) => write!(f, "serial no. {}", serial_number), + None => write!(f, "an unknown serial number"), + } + } +} + +/// Parses a serial number returned by hidapi and transforms it to the Nitrokey format. +/// +/// If the serial number is all zero, this function returns `None`. Otherwise, all leading zeros +/// (except the last eight characters) are stripped and `Some(_)` is returned. If the serial +/// number has less than eight characters, leading zeros are added. +fn get_hidapi_serial_number(serial_number: &str) -> Option { + let mut iter = serial_number.char_indices().skip_while(|(_, c)| *c == '0'); + let first_non_null = iter.next(); + if let Some((i, _)) = first_non_null { + // keep at least the last 8 characters + let len = serial_number.len(); + let cut = cmp::min(len.saturating_sub(8), i); + let (_, suffix) = serial_number.split_at(cut); + // if necessary, add leading zeros to reach 8 characters + let fill = 8usize.saturating_sub(len); + Some("0".repeat(fill) + suffix) + } else { + None + } +} + /// A firmware version for a Nitrokey device. #[derive(Clone, Copy, Debug, PartialEq)] pub struct FirmwareVersion { @@ -476,3 +546,34 @@ pub(crate) fn get_connected_device( pub(crate) fn connect_enum(model: Model) -> bool { unsafe { nitrokey_sys::NK_login_enum(model.into()) == 1 } } + +#[cfg(test)] +mod tests { + use super::get_hidapi_serial_number; + + #[test] + fn hidapi_serial_number() { + assert_eq!(None, get_hidapi_serial_number("")); + assert_eq!(None, get_hidapi_serial_number("00000000000000000")); + assert_eq!( + Some("00001234".to_string()), + get_hidapi_serial_number("1234") + ); + assert_eq!( + Some("00001234".to_string()), + get_hidapi_serial_number("00001234") + ); + assert_eq!( + Some("00001234".to_string()), + get_hidapi_serial_number("000000001234") + ); + assert_eq!( + Some("100000001234".to_string()), + get_hidapi_serial_number("100000001234") + ); + assert_eq!( + Some("10000001234".to_string()), + get_hidapi_serial_number("010000001234") + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 0eb4f40..dad4a20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,8 +118,8 @@ use nitrokey_sys; pub use crate::auth::{Admin, Authenticate, User}; pub use crate::config::Config; pub use crate::device::{ - Device, DeviceWrapper, Model, Pro, SdCardData, Storage, StorageProductionInfo, StorageStatus, - VolumeMode, VolumeStatus, + Device, DeviceInfo, DeviceWrapper, Model, Pro, SdCardData, Storage, StorageProductionInfo, + StorageStatus, VolumeMode, VolumeStatus, }; pub use crate::error::{CommandError, CommunicationError, Error, LibraryError}; pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData}; -- cgit v1.2.3 From c74b8b3ea8dc4fe7c6891ae120540f8da5623227 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 29 Dec 2019 12:51:28 +0100 Subject: Add list_devices function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds support for libnitrokey’s NK_list_devices function by introducing the top-level list_devices function. It returns a vector of DeviceInfo structs with information about all connected Nitrokey devices. --- CHANGELOG.md | 1 + TODO.md | 2 -- src/lib.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/device.rs | 40 +++++++++++++++++++++++++++++---- 4 files changed, 105 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3798540..15c76b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ SPDX-License-Identifier: CC0-1.0 - Implement connection by path: - Add the `Error::UnsupportedDeviceError` variant. - Add the `DeviceInfo` struct. + - Add the `list_devices` function. # v0.4.0 (2020-01-02) - Remove the `test-pro` and `test-storage` features. diff --git a/TODO.md b/TODO.md index 8adea87..e3708f9 100644 --- a/TODO.md +++ b/TODO.md @@ -8,8 +8,6 @@ SPDX-License-Identifier: CC0-1.0 - `NK_get_SD_usage_data` - `NK_get_progress_bar_value` - `NK_get_status` -- waiting for [libnitrokey issue 166][] - - `NK_list_devices` - - `NK_free_device_info` - `NK_connect_with_path` - Clear passwords from memory. - Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware diff --git a/src/lib.rs b/src/lib.rs index dad4a20..5600d24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,8 +109,10 @@ mod otp; mod pws; mod util; +use std::convert::TryInto as _; use std::fmt; use std::marker; +use std::ptr::NonNull; use std::sync; use nitrokey_sys; @@ -126,6 +128,8 @@ pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData}; pub use crate::pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT}; pub use crate::util::LogLevel; +use crate::util::get_last_result; + /// The default admin PIN for all Nitrokey devices. pub const DEFAULT_ADMIN_PIN: &str = "12345678"; /// The default user PIN for all Nitrokey devices. @@ -416,6 +420,70 @@ pub fn force_take() -> Result, Error> { } } +/// List all connected Nitrokey devices. +/// +/// This functions returns a vector with [`DeviceInfo`][] structs that contain information about +/// all connected Nitrokey devices. It will even list unsupported models, although you cannot +/// connect to them. +/// +/// # Errors +/// +/// - [`NotConnected`][] if a Nitrokey device has been disconnected during enumeration +/// - [`Utf8Error`][] if the USB path or the serial number returned by libnitrokey are invalid +/// UTF-8 strings +/// +/// # Example +/// +/// ``` +/// let devices = nitrokey::list_devices()?; +/// if devices.is_empty() { +/// println!("No connected Nitrokey devices found."); +/// } else { +/// println!("model\tpath\tserial number"); +/// for device in devices { +/// match device.model { +/// Some(model) => print!("{}", model), +/// None => print!("unsupported"), +/// } +/// print!("\t{}\t", device.path); +/// match device.serial_number { +/// Some(serial_number) => println!("{}", serial_number), +/// None => println!("unknown"), +/// } +/// } +/// } +/// # Ok::<(), nitrokey::Error>(()) +/// ``` +/// +/// [`DeviceInfo`]: struct.DeviceInfo.html +/// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected +/// [`Utf8Error`]: enum.Error.html#variant.Utf8Error +pub fn list_devices() -> Result, Error> { + let ptr = NonNull::new(unsafe { nitrokey_sys::NK_list_devices() }); + match ptr { + Some(mut ptr) => { + let mut vec: Vec = Vec::new(); + push_device_info(&mut vec, unsafe { ptr.as_ref() })?; + unsafe { + nitrokey_sys::NK_free_device_info(ptr.as_mut()); + } + Ok(vec) + } + None => get_last_result().map(|_| Vec::new()), + } +} + +fn push_device_info( + vec: &mut Vec, + info: &nitrokey_sys::NK_device_info, +) -> Result<(), Error> { + vec.push(info.try_into()?); + if let Some(ptr) = NonNull::new(info.next) { + push_device_info(vec, unsafe { ptr.as_ref() })?; + } + Ok(()) +} + /// 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`][]). diff --git a/tests/device.rs b/tests/device.rs index e367558..5e60002 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -8,8 +8,8 @@ use std::process::Command; use std::{thread, time}; use nitrokey::{ - Authenticate, CommandError, CommunicationError, Config, ConfigureOtp, Device, Error, - GenerateOtp, GetPasswordSafe, LibraryError, OtpMode, OtpSlotData, Storage, VolumeMode, + Authenticate, CommandError, CommunicationError, Config, ConfigureOtp, Device, DeviceInfo, + Error, GenerateOtp, GetPasswordSafe, LibraryError, OtpMode, OtpSlotData, Storage, VolumeMode, DEFAULT_ADMIN_PIN, DEFAULT_USER_PIN, }; use nitrokey_test::test as test_device; @@ -31,6 +31,33 @@ fn count_nitrokey_block_devices() -> usize { .count() } +#[test_device] +fn list_no_devices() { + let devices = nitrokey::list_devices(); + assert_ok!(Vec::::new(), devices); +} + +#[test_device] +fn list_devices(_device: DeviceWrapper) { + let devices = unwrap_ok!(nitrokey::list_devices()); + for device in devices { + assert!(!device.path.is_empty()); + if let Some(model) = device.model { + match model { + nitrokey::Model::Pro => { + assert!(device.serial_number.is_some()); + let serial_number = device.serial_number.unwrap(); + assert!(!serial_number.is_empty()); + assert_valid_serial_number(&serial_number); + }, + nitrokey::Model::Storage => { + assert_eq!(None, device.serial_number); + }, + } + } + } +} + #[test_device] fn connect_no_device() { let mut manager = unwrap_ok!(nitrokey::take()); @@ -83,12 +110,17 @@ fn disconnect(device: DeviceWrapper) { assert_empty_serial_number(); } +fn assert_valid_serial_number(serial_number: &str) { + assert!(serial_number.is_ascii()); + assert!(serial_number.chars().all(|c| c.is_ascii_hexdigit())); +} + #[test_device] fn get_serial_number(device: DeviceWrapper) { let serial_number = unwrap_ok!(device.get_serial_number()); - assert!(serial_number.is_ascii()); - assert!(serial_number.chars().all(|c| c.is_ascii_hexdigit())); + assert_valid_serial_number(&serial_number); } + #[test_device] fn get_firmware_version(device: Pro) { let version = unwrap_ok!(device.get_firmware_version()); -- cgit v1.2.3 From 97c772724dd1fe395e7154e0d71c3b2408436082 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 29 Dec 2019 13:15:04 +0100 Subject: Add the connect_path function to the Manager struct This patch adds the connect_path function to the Manager struct that uses NK_connect_with_path to connect to a Nitrokey device at a given USB path. --- CHANGELOG.md | 1 + TODO.md | 1 - src/lib.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- tests/device.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 98 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15c76b3..abdcab7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ SPDX-License-Identifier: CC0-1.0 - Add the `Error::UnsupportedDeviceError` variant. - Add the `DeviceInfo` struct. - Add the `list_devices` function. + - Add the `connect_path` function to the `Manager` struct. # v0.4.0 (2020-01-02) - Remove the `test-pro` and `test-storage` features. diff --git a/TODO.md b/TODO.md index e3708f9..eba14be 100644 --- a/TODO.md +++ b/TODO.md @@ -8,7 +8,6 @@ SPDX-License-Identifier: CC0-1.0 - `NK_get_SD_usage_data` - `NK_get_progress_bar_value` - `NK_get_status` -- waiting for [libnitrokey issue 166][] - - `NK_connect_with_path` - Clear passwords from memory. - Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware issue 65][]). diff --git a/src/lib.rs b/src/lib.rs index 5600d24..7e8b3ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,10 @@ //! [`connect_model`][], [`connect_pro`][] or [`connect_storage`][] to connect to a specific //! device. //! +//! To get a list of all connected Nitrokey devices, use the [`list_devices`][] function. You can +//! then connect to one of the connected devices using the [`connect_path`][] function of the +//! `Manager` struct. +//! //! You can call [`authenticate_user`][] or [`authenticate_admin`][] to get an authenticated device //! that can perform operations that require authentication. You can use [`device`][] to go back //! to the unauthenticated device. @@ -86,8 +90,10 @@ //! [`take`]: fn.take.html //! [`connect`]: struct.Manager.html#method.connect //! [`connect_model`]: struct.Manager.html#method.connect_model +//! [`connect_path`]: struct.Manager.html#method.connect_path //! [`connect_pro`]: struct.Manager.html#method.connect_pro //! [`connect_storage`]: struct.Manager.html#method.connect_storage +//! [`list_devices`]: fn.list_devices.html //! [`manager`]: trait.Device.html#method.manager //! [`device`]: struct.User.html#method.device //! [`get_hotp_code`]: trait.GenerateOtp.html#method.get_hotp_code @@ -128,7 +134,7 @@ pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData}; pub use crate::pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT}; pub use crate::util::LogLevel; -use crate::util::get_last_result; +use crate::util::{get_cstring, get_last_result}; /// The default admin PIN for all Nitrokey devices. pub const DEFAULT_ADMIN_PIN: &str = "12345678"; @@ -296,6 +302,49 @@ impl Manager { } } + /// Connects to a Nitrokey device at the given USB path. + /// + /// To get a list of all connected Nitrokey devices, use the [`list_devices`][] function. The + /// [`DeviceInfo`][] structs returned by that function contain the USB path in the `path` + /// field. + /// + /// # Errors + /// + /// - [`InvalidString`][] if the USB path contains a null byte + /// - [`NotConnected`][] if no Nitrokey device can be found at the given USB path + /// - [`UnsupportedModelError`][] if the model of the Nitrokey device at the given USB path is + /// not supported by this crate + /// + /// # Example + /// + /// ``` + /// use nitrokey::DeviceWrapper; + /// + /// fn use_device(device: DeviceWrapper) {} + /// + /// let mut manager = nitrokey::take()?; + /// let devices = nitrokey::list_devices()?; + /// for device in devices { + /// let device = manager.connect_path(device.path)?; + /// use_device(device); + /// } + /// # Ok::<(), nitrokey::Error>(()) + /// ``` + /// + /// [`list_devices`]: fn.list_devices.html + /// [`DeviceInfo`]: struct.DeviceInfo.html + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString + /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected + /// [`UnsupportedModelError`]: enum.Error.html#variant.UnsupportedModelError + pub fn connect_path>>(&mut self, path: S) -> Result, Error> { + let path = get_cstring(path)?; + if unsafe { nitrokey_sys::NK_connect_with_path(path.as_ptr()) } == 1 { + device::get_connected_device(self) + } else { + Err(CommunicationError::NotConnected.into()) + } + } + /// Connects to a Nitrokey Pro. /// /// # Errors @@ -424,7 +473,7 @@ pub fn force_take() -> Result, Error> { /// /// This functions returns a vector with [`DeviceInfo`][] structs that contain information about /// all connected Nitrokey devices. It will even list unsupported models, although you cannot -/// connect to them. +/// connect to them. To connect to a supported model, call the [`connect_path`][] function. /// /// # Errors /// @@ -455,6 +504,7 @@ pub fn force_take() -> Result, Error> { /// # Ok::<(), nitrokey::Error>(()) /// ``` /// +/// [`connect_path`]: struct.Manager.html#fn.connect_path /// [`DeviceInfo`]: struct.DeviceInfo.html /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected /// [`Utf8Error`]: enum.Error.html#variant.Utf8Error diff --git a/tests/device.rs b/tests/device.rs index 5e60002..509763b 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -49,10 +49,10 @@ fn list_devices(_device: DeviceWrapper) { let serial_number = device.serial_number.unwrap(); assert!(!serial_number.is_empty()); assert_valid_serial_number(&serial_number); - }, + } nitrokey::Model::Storage => { assert_eq!(None, device.serial_number); - }, + } } } } @@ -104,6 +104,49 @@ fn assert_empty_serial_number() { } } +#[test_device] +fn connect_path_no_device() { + let mut manager = unwrap_ok!(nitrokey::take()); + + assert_cmu_err!(CommunicationError::NotConnected, manager.connect_path("")); + assert_cmu_err!( + CommunicationError::NotConnected, + manager.connect_path("foobar") + ); + // TODO: add realistic path +} + +#[test_device] +fn connect_path(device: DeviceWrapper) { + let manager = device.into_manager(); + + assert_cmu_err!(CommunicationError::NotConnected, manager.connect_path("")); + assert_cmu_err!( + CommunicationError::NotConnected, + manager.connect_path("foobar") + ); + // TODO: add realistic path + + let devices = unwrap_ok!(nitrokey::list_devices()); + assert!(!devices.is_empty()); + for device in devices { + let connected_device = unwrap_ok!(manager.connect_path(device.path)); + assert_eq!(device.model, Some(connected_device.get_model())); + match device.model.unwrap() { + nitrokey::Model::Pro => { + assert!(device.serial_number.is_some()); + assert_ok!( + device.serial_number.unwrap(), + connected_device.get_serial_number() + ); + } + nitrokey::Model::Storage => { + assert_eq!(None, device.serial_number); + } + } + } +} + #[test_device] fn disconnect(device: DeviceWrapper) { drop(device); -- cgit v1.2.3 From dbee55efa41496c8a683bfab96163facc93d6639 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sat, 11 Jan 2020 20:05:39 +0100 Subject: Add support for the GET_STATUS command This patch adds support for the GET_STATUS command that returns the status information common to all Nitrokey devices. It can be accessed using the Device::get_status function and is stored in a Status struct. Due to a bug in the Storage firmware [0], the GET_STATUS command returns wrong firmware versions and serial numbers. Until this is fixed in libnitrokey [1], we have to manually execute the GET_DEVICE_STATUS command to fix these values for the Nitrokey Storage. Also, this leads to a name clash with the existing Storage::get_status function, which will be renamed in an upcoming patch. [0] https://github.com/Nitrokey/nitrokey-storage-firmware/issues/96 [1] https://github.com/Nitrokey/libnitrokey/issues/166 --- CHANGELOG.md | 1 + TODO.md | 3 +-- src/device/mod.rs | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/device/pro.rs | 18 +++++++++++++++- src/device/storage.rs | 31 +++++++++++++++++++++++++-- src/device/wrapper.rs | 9 +++++++- src/lib.rs | 4 ++-- tests/device.rs | 9 ++++++++ 8 files changed, 125 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abdcab7..a9ef359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ SPDX-License-Identifier: CC0-1.0 - Add the `DeviceInfo` struct. - Add the `list_devices` function. - Add the `connect_path` function to the `Manager` struct. +- Add the `get_status` function to the `Device` trait. # v0.4.0 (2020-01-02) - Remove the `test-pro` and `test-storage` features. diff --git a/TODO.md b/TODO.md index eba14be..ed5a0ca 100644 --- a/TODO.md +++ b/TODO.md @@ -7,13 +7,12 @@ SPDX-License-Identifier: CC0-1.0 - `NK_fill_SD_card_with_random_data` - `NK_get_SD_usage_data` - `NK_get_progress_bar_value` - - `NK_get_status` -- waiting for [libnitrokey issue 166][] - Clear passwords from memory. - Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware issue 65][]). - Disable creation of multiple password safes at the same time. - Check timing in Storage tests. - Consider restructuring `device::StorageStatus`. +- Consider renaming `Storage::get_status` (cf. `Device::get_status`). -[libnitrokey issue 166]: https://github.com/Nitrokey/libnitrokey/issues/166 [nitrokey-storage-firmware issue 65]: https://github.com/Nitrokey/nitrokey-storage-firmware/issues/65 diff --git a/src/device/mod.rs b/src/device/mod.rs index e015886..668c230 100644 --- a/src/device/mod.rs +++ b/src/device/mod.rs @@ -153,6 +153,35 @@ impl fmt::Display for FirmwareVersion { } } +/// The status information common to all Nitrokey devices. +pub struct Status { + /// The firmware version of the device. + pub firmware_version: FirmwareVersion, + /// The serial number of the device. + pub serial_number: u32, + /// The configuration of the device. + pub config: Config, +} + +impl From for Status { + fn from(status: nitrokey_sys::NK_status) -> Self { + Self { + firmware_version: FirmwareVersion { + major: status.firmware_version_major, + minor: status.firmware_version_minor, + }, + serial_number: status.serial_number_smart_card, + config: RawConfig { + numlock: status.config_numlock, + capslock: status.config_capslock, + scrollock: status.config_scrolllock, + user_password: status.otp_user_password, + } + .into(), + } + } +} + /// A Nitrokey device. /// /// This trait provides the commands that can be executed without authentication and that are @@ -197,6 +226,35 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// # } fn get_model(&self) -> Model; + /// Returns the status of the Nitrokey device. + /// + /// This methods returns the status information common to all Nitrokey devices as a + /// [`Status`][] struct. Some models may provide more information, for example + /// [`Storage::get_status`][] returns the [`StorageStatus`][] struct. + /// + /// # Errors + /// + /// - [`NotConnected`][] if the Nitrokey device has been disconnected + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// + /// let mut manager = nitrokey::take()?; + /// let device = manager.connect()?; + /// let status = device.get_status()?; + /// println!("Firmware version: {}", status.firmware_version); + /// println!("Serial number: {:x}", status.serial_number); + /// # Ok::<(), nitrokey::Error>(()) + /// ``` + /// + /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected + /// [`Status`]: struct.Status.html + /// [`Storage::get_status`]: struct.Storage.html#method.get_status + /// [`StorageStatus`]: struct.StorageStatus.html + fn get_status(&self) -> Result; + /// Returns the serial number of the Nitrokey device. The serial number is the string /// representation of a hex number. /// diff --git a/src/device/pro.rs b/src/device/pro.rs index a65345e..591b730 100644 --- a/src/device/pro.rs +++ b/src/device/pro.rs @@ -3,8 +3,10 @@ use nitrokey_sys; -use crate::device::{Device, Model}; +use crate::device::{Device, Model, Status}; +use crate::error::Error; use crate::otp::GenerateOtp; +use crate::util::get_command_result; /// A Nitrokey Pro device without user or admin authentication. /// @@ -74,6 +76,20 @@ impl<'a> Device<'a> for Pro<'a> { fn get_model(&self) -> Model { Model::Pro } + + fn get_status(&self) -> Result { + let mut raw_status = nitrokey_sys::NK_status { + firmware_version_major: 0, + firmware_version_minor: 0, + serial_number_smart_card: 0, + config_numlock: 0, + config_capslock: 0, + config_scrolllock: 0, + otp_user_password: false, + }; + get_command_result(unsafe { nitrokey_sys::NK_get_status(&mut raw_status) })?; + Ok(raw_status.into()) + } } impl<'a> GenerateOtp for Pro<'a> {} diff --git a/src/device/storage.rs b/src/device/storage.rs index 370ce36..64e6848 100644 --- a/src/device/storage.rs +++ b/src/device/storage.rs @@ -1,11 +1,11 @@ -// Copyright (C) 2018-2019 Robin Krahl +// Copyright (C) 2019-2019 Robin Krahl // SPDX-License-Identifier: MIT use std::fmt; use nitrokey_sys; -use crate::device::{Device, FirmwareVersion, Model}; +use crate::device::{Device, FirmwareVersion, Model, Status}; use crate::error::Error; use crate::otp::GenerateOtp; use crate::util::{get_command_result, get_cstring}; @@ -678,6 +678,33 @@ impl<'a> Device<'a> for Storage<'a> { fn get_model(&self) -> Model { Model::Storage } + + fn get_status(&self) -> Result { + // Currently, the GET_STATUS command does not report the correct firmware version and + // serial number on the Nitrokey Storage, see [0]. Until this is fixed in libnitrokey, we + // have to manually execute the GET_DEVICE_STATUS command (Storage::get_status) and + // complete the missing data, see [1]. + // [0] https://github.com/Nitrokey/nitrokey-storage-firmware/issues/96 + // [1] https://github.com/Nitrokey/libnitrokey/issues/166 + + let mut raw_status = nitrokey_sys::NK_status { + firmware_version_major: 0, + firmware_version_minor: 0, + serial_number_smart_card: 0, + config_numlock: 0, + config_capslock: 0, + config_scrolllock: 0, + otp_user_password: false, + }; + get_command_result(unsafe { nitrokey_sys::NK_get_status(&mut raw_status) })?; + let mut status = Status::from(raw_status); + + let storage_status = Storage::get_status(&self)?; + status.firmware_version = storage_status.firmware_version; + status.serial_number = storage_status.serial_number_smart_card; + + Ok(status) + } } impl<'a> GenerateOtp for Storage<'a> {} diff --git a/src/device/wrapper.rs b/src/device/wrapper.rs index a3a18f9..adbb695 100644 --- a/src/device/wrapper.rs +++ b/src/device/wrapper.rs @@ -1,7 +1,7 @@ // Copyright (C) 2018-2019 Robin Krahl // SPDX-License-Identifier: MIT -use crate::device::{Device, Model, Pro, Storage}; +use crate::device::{Device, Model, Pro, Status, Storage}; use crate::error::Error; use crate::otp::GenerateOtp; @@ -131,4 +131,11 @@ impl<'a> Device<'a> for DeviceWrapper<'a> { DeviceWrapper::Storage(_) => Model::Storage, } } + + fn get_status(&self) -> Result { + match self { + DeviceWrapper::Pro(dev) => dev.get_status(), + DeviceWrapper::Storage(dev) => Device::get_status(dev), + } + } } diff --git a/src/lib.rs b/src/lib.rs index 7e8b3ef..41c99c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,8 +126,8 @@ use nitrokey_sys; pub use crate::auth::{Admin, Authenticate, User}; pub use crate::config::Config; pub use crate::device::{ - Device, DeviceInfo, DeviceWrapper, Model, Pro, SdCardData, Storage, StorageProductionInfo, - StorageStatus, VolumeMode, VolumeStatus, + Device, DeviceInfo, DeviceWrapper, Model, Pro, SdCardData, Status, Storage, + StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus, }; pub use crate::error::{CommandError, CommunicationError, Error, LibraryError}; pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData}; diff --git a/tests/device.rs b/tests/device.rs index 509763b..92b79bd 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -153,6 +153,15 @@ fn disconnect(device: DeviceWrapper) { assert_empty_serial_number(); } +#[test_device] +fn get_status(device: DeviceWrapper) { + let status = unwrap_ok!(device.get_status()); + assert_ok!(status.firmware_version, device.get_firmware_version()); + let serial_number = format!("{:08x}", status.serial_number); + assert_ok!(serial_number, device.get_serial_number()); + assert_ok!(status.config, device.get_config()); +} + fn assert_valid_serial_number(serial_number: &str) { assert!(serial_number.is_ascii()); assert!(serial_number.chars().all(|c| c.is_ascii_hexdigit())); -- cgit v1.2.3 From 6142752da1563c1ab873dc7069aeec72522cca99 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sat, 11 Jan 2020 20:13:22 +0100 Subject: Rename Status::get_status to get_storage_status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the last patch, we added the get_status function to the Device trait. This patch renames the Storage::get_status function to get_storage_status to resolve the name clash – though allowed by the compiler, it is rather confusing for the end user. --- CHANGELOG.md | 1 + TODO.md | 1 - src/device/mod.rs | 4 ++-- src/device/storage.rs | 10 +++++----- src/device/wrapper.rs | 2 +- tests/device.rs | 10 +++++----- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9ef359..b477c3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ SPDX-License-Identifier: CC0-1.0 - Add the `list_devices` function. - Add the `connect_path` function to the `Manager` struct. - Add the `get_status` function to the `Device` trait. +- Rename `Status::get_status` to `get_storage_status`. # v0.4.0 (2020-01-02) - Remove the `test-pro` and `test-storage` features. diff --git a/TODO.md b/TODO.md index ed5a0ca..aaa6856 100644 --- a/TODO.md +++ b/TODO.md @@ -13,6 +13,5 @@ SPDX-License-Identifier: CC0-1.0 - Disable creation of multiple password safes at the same time. - Check timing in Storage tests. - Consider restructuring `device::StorageStatus`. -- Consider renaming `Storage::get_status` (cf. `Device::get_status`). [nitrokey-storage-firmware issue 65]: https://github.com/Nitrokey/nitrokey-storage-firmware/issues/65 diff --git a/src/device/mod.rs b/src/device/mod.rs index 668c230..ef9f018 100644 --- a/src/device/mod.rs +++ b/src/device/mod.rs @@ -230,7 +230,7 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// /// This methods returns the status information common to all Nitrokey devices as a /// [`Status`][] struct. Some models may provide more information, for example - /// [`Storage::get_status`][] returns the [`StorageStatus`][] struct. + /// [`get_storage_status`][] returns the [`StorageStatus`][] struct. /// /// # Errors /// @@ -249,9 +249,9 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt /// # Ok::<(), nitrokey::Error>(()) /// ``` /// + /// [`get_storage_status`]: struct.Storage.html#method.get_storage_status /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected /// [`Status`]: struct.Status.html - /// [`Storage::get_status`]: struct.Storage.html#method.get_status /// [`StorageStatus`]: struct.StorageStatus.html fn get_status(&self) -> Result; diff --git a/src/device/storage.rs b/src/device/storage.rs index 64e6848..08ec4e9 100644 --- a/src/device/storage.rs +++ b/src/device/storage.rs @@ -525,7 +525,7 @@ impl<'a> Storage<'a> { /// # fn try_main() -> Result<(), Error> { /// let mut manager = nitrokey::take()?; /// let device = manager.connect_storage()?; - /// match device.get_status() { + /// match device.get_storage_status() { /// Ok(status) => { /// println!("SD card ID: {:#x}", status.serial_number_sd_card); /// }, @@ -534,7 +534,7 @@ impl<'a> Storage<'a> { /// # Ok(()) /// # } /// ``` - pub fn get_status(&self) -> Result { + pub fn get_storage_status(&self) -> Result { let mut raw_status = nitrokey_sys::NK_storage_status { unencrypted_volume_read_only: false, unencrypted_volume_active: false, @@ -682,8 +682,8 @@ impl<'a> Device<'a> for Storage<'a> { fn get_status(&self) -> Result { // Currently, the GET_STATUS command does not report the correct firmware version and // serial number on the Nitrokey Storage, see [0]. Until this is fixed in libnitrokey, we - // have to manually execute the GET_DEVICE_STATUS command (Storage::get_status) and - // complete the missing data, see [1]. + // have to manually execute the GET_DEVICE_STATUS command (get_storage_status) and complete + // the missing data, see [1]. // [0] https://github.com/Nitrokey/nitrokey-storage-firmware/issues/96 // [1] https://github.com/Nitrokey/libnitrokey/issues/166 @@ -699,7 +699,7 @@ impl<'a> Device<'a> for Storage<'a> { get_command_result(unsafe { nitrokey_sys::NK_get_status(&mut raw_status) })?; let mut status = Status::from(raw_status); - let storage_status = Storage::get_status(&self)?; + let storage_status = self.get_storage_status()?; status.firmware_version = storage_status.firmware_version; status.serial_number = storage_status.serial_number_smart_card; diff --git a/src/device/wrapper.rs b/src/device/wrapper.rs index adbb695..69291ad 100644 --- a/src/device/wrapper.rs +++ b/src/device/wrapper.rs @@ -135,7 +135,7 @@ impl<'a> Device<'a> for DeviceWrapper<'a> { fn get_status(&self) -> Result { match self { DeviceWrapper::Pro(dev) => dev.get_status(), - DeviceWrapper::Storage(dev) => Device::get_status(dev), + DeviceWrapper::Storage(dev) => dev.get_status(), } } } diff --git a/tests/device.rs b/tests/device.rs index 92b79bd..f2a7031 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -564,7 +564,7 @@ fn set_encrypted_volume_mode(device: Storage) { #[test_device] fn set_unencrypted_volume_mode(device: Storage) { fn assert_mode(device: &Storage, mode: VolumeMode) { - let status = unwrap_ok!(device.get_status()); + let status = unwrap_ok!(device.get_storage_status()); assert_eq!( status.unencrypted_volume.read_only, mode == VolumeMode::ReadOnly @@ -595,7 +595,7 @@ fn set_unencrypted_volume_mode(device: Storage) { #[test_device] fn get_storage_status(device: Storage) { - let status = unwrap_ok!(device.get_status()); + let status = unwrap_ok!(device.get_storage_status()); assert!(status.serial_number_sd_card > 0); assert!(status.serial_number_smart_card > 0); } @@ -615,7 +615,7 @@ fn get_production_info(device: Storage) { assert!(info.sd_card.oem != 0); assert!(info.sd_card.manufacturer != 0); - let status = unwrap_ok!(device.get_status()); + let status = unwrap_ok!(device.get_storage_status()); assert_eq!(status.firmware_version, info.firmware_version); assert_eq!(status.serial_number_sd_card, info.sd_card.serial_number); } @@ -630,12 +630,12 @@ fn clear_new_sd_card_warning(device: Storage) { // We have to perform an SD card operation to reset the new_sd_card_found field assert_ok!((), device.lock()); - let status = unwrap_ok!(device.get_status()); + let status = unwrap_ok!(device.get_storage_status()); assert!(status.new_sd_card_found); assert_ok!((), device.clear_new_sd_card_warning(DEFAULT_ADMIN_PIN)); - let status = unwrap_ok!(device.get_status()); + let status = unwrap_ok!(device.get_storage_status()); assert!(!status.new_sd_card_found); } -- cgit v1.2.3 From 73c8aebac338d3454d7e345ffd687324317077ec Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 14 Jan 2020 12:00:15 +0100 Subject: Add the get_sd_card_usage function to the Storage struct This patch adds support for the NK_get_SD_usage_data function. It returns a range of the SD card that has not been accessed during this power cycle. --- CHANGELOG.md | 1 + TODO.md | 1 - src/device/storage.rs | 42 +++++++++++++++++++++++++++++++++++++++++- tests/device.rs | 10 +++++++++- 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b477c3b..cebe8cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ SPDX-License-Identifier: CC0-1.0 - Add the `connect_path` function to the `Manager` struct. - Add the `get_status` function to the `Device` trait. - Rename `Status::get_status` to `get_storage_status`. +- Add the `get_sd_card_usage` function to the `Storage` struct. # v0.4.0 (2020-01-02) - Remove the `test-pro` and `test-storage` features. diff --git a/TODO.md b/TODO.md index aaa6856..3c095e0 100644 --- a/TODO.md +++ b/TODO.md @@ -5,7 +5,6 @@ SPDX-License-Identifier: CC0-1.0 - Add support for the currently unsupported commands: - `NK_fill_SD_card_with_random_data` - - `NK_get_SD_usage_data` - `NK_get_progress_bar_value` - Clear passwords from memory. - Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware diff --git a/src/device/storage.rs b/src/device/storage.rs index 08ec4e9..a2c598b 100644 --- a/src/device/storage.rs +++ b/src/device/storage.rs @@ -1,7 +1,8 @@ -// Copyright (C) 2019-2019 Robin Krahl +// Copyright (C) 2019-2020 Robin Krahl // SPDX-License-Identifier: MIT use std::fmt; +use std::ops; use nitrokey_sys; @@ -635,6 +636,45 @@ impl<'a> Storage<'a> { }) } + /// Returns a range of the SD card that has not been used to during this power cycle. + /// + /// The Nitrokey Storage tracks read and write access to the SD card during a power cycle. + /// This method returns a range of the SD card that has not been accessed during this power + /// cycle. The range is relative to the total size of the SD card, so both values are less + /// than or equal to 100. This can be used as a guideline when creating a hidden volume. + /// + /// # Example + /// + /// ```no_run + /// let mut manager = nitrokey::take()?; + /// let storage = manager.connect_storage()?; + /// let usage = storage.get_sd_card_usage()?; + /// println!("SD card usage: {}..{}", usage.start, usage.end); + /// # Ok::<(), nitrokey::Error>(()) + /// ``` + pub fn get_sd_card_usage(&self) -> Result, Error> { + let mut usage_data = nitrokey_sys::NK_SD_usage_data { + write_level_min: 0, + write_level_max: 0, + }; + let result = unsafe { nitrokey_sys::NK_get_SD_usage_data(&mut usage_data) }; + match get_command_result(result) { + Ok(_) => { + if usage_data.write_level_min > usage_data.write_level_max + || usage_data.write_level_max > 100 + { + Err(Error::UnexpectedError) + } else { + Ok(ops::Range { + start: usage_data.write_level_min, + end: usage_data.write_level_max, + }) + } + } + Err(err) => Err(err), + } + } + /// Blinks the red and green LED alternatively and infinitely until the device is reconnected. pub fn wink(&mut self) -> Result<(), Error> { get_command_result(unsafe { nitrokey_sys::NK_wink() }) diff --git a/tests/device.rs b/tests/device.rs index f2a7031..070f3c1 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2018-2019 Robin Krahl +// Copyright (C) 2018-2020 Robin Krahl // SPDX-License-Identifier: MIT mod util; @@ -639,6 +639,14 @@ fn clear_new_sd_card_warning(device: Storage) { assert!(!status.new_sd_card_found); } +#[test_device] +fn get_sd_card_usage(device: Storage) { + let range = unwrap_ok!(device.get_sd_card_usage()); + + assert!(range.end >= range.start); + assert!(range.end <= 100); +} + #[test_device] fn export_firmware(device: Storage) { let mut device = device; -- cgit v1.2.3 From 45b2bc5c77a2f5c22ee6a75c1c1d2b4f9b458943 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 14 Jan 2020 12:25:41 +0100 Subject: Remove todo notes for Storage tests and refactoring This patch removes two items from the todo list: - Check timing in Storage tests: Since we introduced the two seconds timeout, no test failed due to timing issues. There is nothing we can do to check it more precisely, so we just have to live with it for now. - Consider restructuring `device::StorageStatus`: We already have structs for the volume status and firmware version. There is no need to extract more fields. --- TODO.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/TODO.md b/TODO.md index 3c095e0..79a68e4 100644 --- a/TODO.md +++ b/TODO.md @@ -10,7 +10,5 @@ SPDX-License-Identifier: CC0-1.0 - Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware issue 65][]). - Disable creation of multiple password safes at the same time. -- Check timing in Storage tests. -- Consider restructuring `device::StorageStatus`. [nitrokey-storage-firmware issue 65]: https://github.com/Nitrokey/nitrokey-storage-firmware/issues/65 -- cgit v1.2.3 From eb04dafce313ff5349b1c93d8d87cb53ba320e7e Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 14 Jan 2020 16:18:02 +0100 Subject: Derive Clone, Copy, Debug, PartialEq for Status Somehow I forgot to derive the common traits for the new Status struct. This patch adds the missing derive attribute for Clone, Copy, Debug and PartialEq. --- src/device/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/device/mod.rs b/src/device/mod.rs index ef9f018..8b7f1c6 100644 --- a/src/device/mod.rs +++ b/src/device/mod.rs @@ -154,6 +154,7 @@ impl fmt::Display for FirmwareVersion { } /// The status information common to all Nitrokey devices. +#[derive(Clone, Copy, Debug, PartialEq)] pub struct Status { /// The firmware version of the device. pub firmware_version: FirmwareVersion, -- cgit v1.2.3 From f266ea63039c87886f871b068ef3dcdf851a1eca Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 14 Jan 2020 13:48:56 +0100 Subject: Add the get_operation_status function to the Storage struct This patch adds support for the NK_get_progress_bar_value function: It adds the OperationStatus enum that stores the return value of this command and adds the get_operation_status function to the Storage struct that executes the command. --- CHANGELOG.md | 2 ++ TODO.md | 1 - src/device/mod.rs | 3 ++- src/device/storage.rs | 35 ++++++++++++++++++++++++++++++++++- src/lib.rs | 2 +- tests/device.rs | 9 +++++++-- 6 files changed, 46 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cebe8cb..419a942 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ SPDX-License-Identifier: CC0-1.0 - Add the `get_status` function to the `Device` trait. - Rename `Status::get_status` to `get_storage_status`. - Add the `get_sd_card_usage` function to the `Storage` struct. +- Add the `OperationStatus` enum and the `get_operation_status` function for + the `Storage` struct. # v0.4.0 (2020-01-02) - Remove the `test-pro` and `test-storage` features. diff --git a/TODO.md b/TODO.md index 79a68e4..8f9b898 100644 --- a/TODO.md +++ b/TODO.md @@ -5,7 +5,6 @@ SPDX-License-Identifier: CC0-1.0 - Add support for the currently unsupported commands: - `NK_fill_SD_card_with_random_data` - - `NK_get_progress_bar_value` - Clear passwords from memory. - Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware issue 65][]). diff --git a/src/device/mod.rs b/src/device/mod.rs index 8b7f1c6..16d6b11 100644 --- a/src/device/mod.rs +++ b/src/device/mod.rs @@ -25,7 +25,8 @@ use crate::util::{ pub use pro::Pro; pub use storage::{ - SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus, + OperationStatus, SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode, + VolumeStatus, }; pub use wrapper::DeviceWrapper; diff --git a/src/device/storage.rs b/src/device/storage.rs index a2c598b..8d45763 100644 --- a/src/device/storage.rs +++ b/src/device/storage.rs @@ -1,6 +1,7 @@ // Copyright (C) 2019-2020 Robin Krahl // SPDX-License-Identifier: MIT +use std::convert::TryFrom as _; use std::fmt; use std::ops; @@ -9,7 +10,7 @@ use nitrokey_sys; use crate::device::{Device, FirmwareVersion, Model, Status}; use crate::error::Error; use crate::otp::GenerateOtp; -use crate::util::{get_command_result, get_cstring}; +use crate::util::{get_command_result, get_cstring, get_last_error}; /// A Nitrokey Storage device without user or admin authentication. /// @@ -142,6 +143,20 @@ pub struct StorageStatus { pub stick_initialized: bool, } +/// The progress of a background operation on the Nitrokey. +/// +/// Some commands may start a background operation during which no other commands can be executed. +/// This enum stores the status of a background operation: Ongoing with a relative progress (up to +/// 100), or idle, i. e. no background operation has been started or the last one has been +/// finished. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum OperationStatus { + /// A background operation with its progress value (less than or equal to 100). + Ongoing(u8), + /// No backgrund operation. + Idle, +} + impl<'a> Storage<'a> { pub(crate) fn new(manager: &'a mut crate::Manager) -> Storage<'a> { Storage { @@ -680,6 +695,24 @@ impl<'a> Storage<'a> { get_command_result(unsafe { nitrokey_sys::NK_wink() }) } + /// Returns the status of an ongoing background operation on the Nitrokey Storage. + /// + /// Some commands may start a background operation during which no other commands can be + /// executed. This method can be used to check whether such an operation is ongoing. + /// + /// Currently, this is only used by the `fill_sd_card` method. + pub fn get_operation_status(&self) -> Result { + let status = unsafe { nitrokey_sys::NK_get_progress_bar_value() }; + match status { + 0..=100 => u8::try_from(status) + .map(OperationStatus::Ongoing) + .map_err(|_| Error::UnexpectedError), + -1 => Ok(OperationStatus::Idle), + -2 => Err(get_last_error()), + _ => Err(Error::UnexpectedError), + } + } + /// Exports the firmware to the unencrypted volume. /// /// This command requires the admin PIN. The unencrypted volume must be in read-write mode diff --git a/src/lib.rs b/src/lib.rs index 41c99c5..c6c3ff5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,7 +126,7 @@ use nitrokey_sys; pub use crate::auth::{Admin, Authenticate, User}; pub use crate::config::Config; pub use crate::device::{ - Device, DeviceInfo, DeviceWrapper, Model, Pro, SdCardData, Status, Storage, + Device, DeviceInfo, DeviceWrapper, Model, OperationStatus, Pro, SdCardData, Status, Storage, StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus, }; pub use crate::error::{CommandError, CommunicationError, Error, LibraryError}; diff --git a/tests/device.rs b/tests/device.rs index 070f3c1..7296372 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -9,8 +9,8 @@ use std::{thread, time}; use nitrokey::{ Authenticate, CommandError, CommunicationError, Config, ConfigureOtp, Device, DeviceInfo, - Error, GenerateOtp, GetPasswordSafe, LibraryError, OtpMode, OtpSlotData, Storage, VolumeMode, - DEFAULT_ADMIN_PIN, DEFAULT_USER_PIN, + Error, GenerateOtp, GetPasswordSafe, LibraryError, OperationStatus, OtpMode, OtpSlotData, + Storage, VolumeMode, DEFAULT_ADMIN_PIN, DEFAULT_USER_PIN, }; use nitrokey_test::test as test_device; @@ -647,6 +647,11 @@ fn get_sd_card_usage(device: Storage) { assert!(range.end <= 100); } +#[test_device] +fn get_operation_status(device: Storage) { + assert_ok!(OperationStatus::Idle, device.get_operation_status()); +} + #[test_device] fn export_firmware(device: Storage) { let mut device = device; -- cgit v1.2.3 From 2e543445c3059fa9decdbef718caf84696bb8786 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 14 Jan 2020 16:15:40 +0100 Subject: Add the fill_sd_card function to Storage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds support for libnitrokey’s NK_fill_SD_card_with_random_data function. It is executed by the fill_sd_card function of the Storage struct. We also add a new test case that is set to ignore because it takes between 30 and 60 minutes to run. --- CHANGELOG.md | 1 + README.md | 4 ++++ TODO.md | 2 -- src/device/storage.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++++-- tests/device.rs | 39 +++++++++++++++++++++++++++++++++++++ 5 files changed, 95 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 419a942..06836d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ SPDX-License-Identifier: CC0-1.0 - Add the `get_sd_card_usage` function to the `Storage` struct. - Add the `OperationStatus` enum and the `get_operation_status` function for the `Storage` struct. +- Add the `fill_sd_card` function to the `Storage` struct. # v0.4.0 (2020-01-02) - Remove the `test-pro` and `test-storage` features. diff --git a/README.md b/README.md index d62e38e..5f775ef 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,10 @@ an AES key has been built. Some tests will overwrite the data stored on the Nitrokey device or perform a factory reset. Never execute the tests if you don’t want to destroy all data on any connected Nitrokey device! +The test suite contains some test that take very long to execute, for example +filling the SD card of a Nitrokey Storage with random data. These tests are +ignored per default. Use `cargo test -- --ignored` to execute the tests. + ## Acknowledgments Thanks to Nitrokey UG for providing two Nitrokey devices to support the diff --git a/TODO.md b/TODO.md index 8f9b898..ef2f6d4 100644 --- a/TODO.md +++ b/TODO.md @@ -3,8 +3,6 @@ Copyright (C) 2019 Robin Krahl SPDX-License-Identifier: CC0-1.0 --> -- Add support for the currently unsupported commands: - - `NK_fill_SD_card_with_random_data` - Clear passwords from memory. - Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware issue 65][]). diff --git a/src/device/storage.rs b/src/device/storage.rs index 8d45763..deb2844 100644 --- a/src/device/storage.rs +++ b/src/device/storage.rs @@ -8,7 +8,7 @@ use std::ops; use nitrokey_sys; use crate::device::{Device, FirmwareVersion, Model, Status}; -use crate::error::Error; +use crate::error::{CommandError, Error}; use crate::otp::GenerateOtp; use crate::util::{get_command_result, get_cstring, get_last_error}; @@ -700,7 +700,9 @@ impl<'a> Storage<'a> { /// Some commands may start a background operation during which no other commands can be /// executed. This method can be used to check whether such an operation is ongoing. /// - /// Currently, this is only used by the `fill_sd_card` method. + /// Currently, this is only used by the [`fill_sd_card`][] method. + /// + /// [`fill_sd_card`]: #method.fill_sd_card pub fn get_operation_status(&self) -> Result { let status = unsafe { nitrokey_sys::NK_get_progress_bar_value() }; match status { @@ -713,6 +715,53 @@ impl<'a> Storage<'a> { } } + /// Overwrites the SD card with random data. + /// + /// Ths method starts a background operation that overwrites the SD card with random data. + /// While this operation is ongoing, no other commands can be executed. Use the + /// [`get_operation_status`][] function to check the progress of the operation. + /// + /// # Errors + /// + /// - [`InvalidString`][] if one of the provided passwords contains a null byte + /// - [`WrongPassword`][] if the admin password is wrong + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::OperationStatus; + /// + /// let mut manager = nitrokey::take()?; + /// let mut storage = manager.connect_storage()?; + /// storage.fill_sd_card("12345678")?; + /// loop { + /// match storage.get_operation_status()? { + /// OperationStatus::Ongoing(progress) => println!("{}/100", progress), + /// OperationStatus::Idle => { + /// println!("Done!"); + /// break; + /// } + /// } + /// } + /// # Ok::<(), nitrokey::Error>(()) + /// ``` + /// + /// [`get_operation_status`]: #method.get_operation_status + /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString + /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword + pub fn fill_sd_card(&mut self, admin_pin: &str) -> Result<(), Error> { + let admin_pin_string = get_cstring(admin_pin)?; + get_command_result(unsafe { + nitrokey_sys::NK_fill_SD_card_with_random_data(admin_pin_string.as_ptr()) + }) + .or_else(|err| match err { + // libnitrokey’s C API returns a LongOperationInProgressException with the same error + // code as the WrongCrc command error, so we cannot distinguish them. + Error::CommandError(CommandError::WrongCrc) => Ok(()), + err => Err(err), + }) + } + /// Exports the firmware to the unencrypted volume. /// /// This command requires the admin PIN. The unencrypted volume must be in read-write mode diff --git a/tests/device.rs b/tests/device.rs index 7296372..a88c956 100644 --- a/tests/device.rs +++ b/tests/device.rs @@ -652,6 +652,45 @@ fn get_operation_status(device: Storage) { assert_ok!(OperationStatus::Idle, device.get_operation_status()); } +#[test_device] +#[ignore] +fn fill_sd_card(device: Storage) { + // This test takes up to 60 min to execute and is therefore ignored by default. Use `cargo + // test -- --ignored fill_sd_card` to run the test. + + let mut device = device; + assert_ok!((), device.factory_reset(DEFAULT_ADMIN_PIN)); + thread::sleep(time::Duration::from_secs(3)); + assert_ok!((), device.build_aes_key(DEFAULT_ADMIN_PIN)); + + let status = unwrap_ok!(device.get_storage_status()); + assert!(!status.filled_with_random); + + assert_ok!((), device.fill_sd_card(DEFAULT_ADMIN_PIN)); + assert_cmd_err!(CommandError::WrongCrc, device.get_status()); + + let mut status = OperationStatus::Ongoing(0); + let mut last_progress = 0u8; + while status != OperationStatus::Idle { + status = unwrap_ok!(device.get_operation_status()); + if let OperationStatus::Ongoing(progress) = status { + assert!(progress <= 100, "progress = {}", progress); + assert!( + progress >= last_progress, + "progress = {}, last_progress = {}", + progress, + last_progress + ); + last_progress = progress; + + thread::sleep(time::Duration::from_secs(10)); + } + } + + let status = unwrap_ok!(device.get_storage_status()); + assert!(status.filled_with_random); +} + #[test_device] fn export_firmware(device: Storage) { let mut device = device; -- cgit v1.2.3 From bfe261bd3e2517021bad36612889c44a8b2327dd Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 14 Jan 2020 16:33:11 +0100 Subject: Document background operations This patch adds a new section about background operations to the crate documentation. --- src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index c6c3ff5..9efad91 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,15 @@ //! passwords – [`get_hotp_code`][] and [`get_totp_code`][]. Depending on the stick configuration, //! these operations are available without authentication or with user authentication. //! +//! # Background operations +//! +//! Some commands may start background operations. During such an operation, every new command +//! will cause a [`WrongCrc`][] error. To check whether a background operation is currently +//! running, use the [`get_operation_status`][] method. +//! +//! Background operations are only available on the Nitrokey Storage. Currently, +//! [`fill_sd_card`][] is the only command that triggers a background operation. +//! //! # Examples //! //! Connect to any Nitrokey and print its serial number: @@ -93,6 +102,8 @@ //! [`connect_path`]: struct.Manager.html#method.connect_path //! [`connect_pro`]: struct.Manager.html#method.connect_pro //! [`connect_storage`]: struct.Manager.html#method.connect_storage +//! [`fill_sd_card`]: struct.Storage.html#method.fill_sd_card +//! [`get_operation_status`]: struct.Storage.html#method.get_operation_status //! [`list_devices`]: fn.list_devices.html //! [`manager`]: trait.Device.html#method.manager //! [`device`]: struct.User.html#method.device @@ -101,6 +112,7 @@ //! [`Admin`]: struct.Admin.html //! [`DeviceWrapper`]: enum.DeviceWrapper.html //! [`User`]: struct.User.html +//! [`WrongCrc`]: enum.CommandError.html#variant.WrongCrc #![warn(missing_docs, rust_2018_compatibility, rust_2018_idioms, unused)] -- cgit v1.2.3 From 698f6cbbe1539369d8857739df11425435af1daf Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 14 Jan 2020 17:31:26 +0100 Subject: Add basic usage examples This patch adds two basic usage examples: listing and connecting to Nitrokey devices, and generating a one-time password. --- examples/list-devices.rs | 26 ++++++++++++++++++++++++++ examples/otp.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 examples/list-devices.rs create mode 100644 examples/otp.rs diff --git a/examples/list-devices.rs b/examples/list-devices.rs new file mode 100644 index 0000000..9c14533 --- /dev/null +++ b/examples/list-devices.rs @@ -0,0 +1,26 @@ +// Copyright (C) 2020 Robin Krahl +// SPDX-License-Identifier: CC-0 + +//! Enumerates all connected Nitrokey devices and prints some information about them. + +use nitrokey::Device as _; + +fn main() -> Result<(), nitrokey::Error> { + let mut manager = nitrokey::take()?; + let device_infos = nitrokey::list_devices()?; + if device_infos.is_empty() { + println!("No Nitrokey device found"); + } else { + println!("path\t\tmodel\tfirmware version\tserial number"); + for device_info in device_infos { + let device = manager.connect_path(device_info.path.clone())?; + let model = device.get_model(); + let status = device.get_status()?; + println!( + "{}\t{}\t{}\t\t\t{:08x}", + device_info.path, model, status.firmware_version, status.serial_number + ); + } + } + Ok(()) +} diff --git a/examples/otp.rs b/examples/otp.rs new file mode 100644 index 0000000..819de28 --- /dev/null +++ b/examples/otp.rs @@ -0,0 +1,43 @@ +// Copyright (C) 2020 Robin Krahl +// SPDX-License-Identifier: CC-0 + +//! Connects to a Nitrokey device, configures an TOTP slot and generates a one-time password from +//! it. + +use std::time; + +use nitrokey::{Authenticate, ConfigureOtp, Device, GenerateOtp}; + +fn main() -> Result<(), nitrokey::Error> { + let mut manager = nitrokey::take()?; + let device = manager.connect()?; + + // Configure the OTP slot (requires admin PIN) + let data = nitrokey::OtpSlotData::new( + 1, + "test", + "3132333435363738393031323334353637383930", + nitrokey::OtpMode::SixDigits, + ); + let mut admin = device.authenticate_admin("12345678")?; + admin.write_totp_slot(data, 30)?; + let mut device = admin.device(); + + // Set the time for the OTP generation + let time = time::SystemTime::now() + .duration_since(time::UNIX_EPOCH) + .expect("Invalid system time"); + device.set_time(time.as_secs(), true)?; + + // Generate a one-time password -- depending on the configuration, we have to set the user PIN + let config = device.get_config()?; + let otp = if config.user_password { + let user = device.authenticate_user("123456")?; + user.get_totp_code(1) + } else { + device.get_totp_code(1) + }?; + println!("Generated OTP code: {}", otp); + + Ok(()) +} -- cgit v1.2.3 From 0712d48132b172837ba9e63e5fbe904a88919ce7 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 14 Jan 2020 17:42:54 +0100 Subject: Remove multiple PWS item from todo list With commit f49e61589e32217f97c94aa86d826f6b65170fba, we changed the GetPasswordSafe::get_password_safe to take a mutable reference to a device. This makes sure that we cannot have two PWS instances for one device at the same time, so we can drop the corresponding item from the todo list. --- TODO.md | 1 - 1 file changed, 1 deletion(-) diff --git a/TODO.md b/TODO.md index ef2f6d4..92d4b04 100644 --- a/TODO.md +++ b/TODO.md @@ -6,6 +6,5 @@ SPDX-License-Identifier: CC0-1.0 - Clear passwords from memory. - Lock password safe in `PasswordSafe::drop()` (see [nitrokey-storage-firmware issue 65][]). -- Disable creation of multiple password safes at the same time. [nitrokey-storage-firmware issue 65]: https://github.com/Nitrokey/nitrokey-storage-firmware/issues/65 -- cgit v1.2.3 From 1bc96cf8391977358f77741c63298894f52b7aff Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 14 Jan 2020 18:01:20 +0100 Subject: Update readme: usage, features, system libnitrokey This patch updates the readme with: - more usage information, especially a pointer to the examples and to nitrocli, - information about the supported features (= everything), - information on how to use the system libnitrokey instead of building it from source. --- README.md | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5f775ef..8ce093e 100644 --- a/README.md +++ b/README.md @@ -7,19 +7,28 @@ SPDX-License-Identifier: CC0-1.0 A libnitrokey wrapper for Rust providing access to Nitrokey devices. -[Documentation][] +## Usage + +For usage information, have a look at the [API reference][API reference] and at +the [examples][] in the `examples` directory. You can also have a look at the +[`nitrocli`][] crate, a command-line interface for Nitrokey devices that uses +this crate. ## Compatibility -The required [`libnitrokey`][] version is built from source. The host system +This crate provides access to all features of the [`libnitrokey`][] C API for +both the Nitrokey Pro and the Nitrokey Storage: general configuration, one-time +password generation, the password safe and the secure storage on the Nitrokey +Storage. + +The required `libnitrokey` version is built from source. The host system must provide `libhidapi-libusb0` (Linux) or `libhidapi` (non-Linux) in the default library search path. Depending on your system, you might also have to install the [Nitrokey udev rules][]. -Currently, this crate provides access to the common features of the Nitrokey -Pro and the Nitrokey Storage: general configuration, OTP generation and the -password safe. Basic support for the secure storage on the Nitrokey Storage is -available but still under development. +If you want to use a precompiled version of `libnitrokey`, you can set the +`USE_SYSTEM_LIBNITROKEY` environment variable during build. In this case, +`libnitrokey` must be available in the library search path. ### Unsupported Functions @@ -92,7 +101,9 @@ in the `LICENSES` directory. `libnitrokey` is licensed under the [LGPL-3.0][]. `nitrokey-rs` complies with [version 3.0 of the REUSE specification][reuse]. -[Documentation]: https://docs.rs/nitrokey +[API reference]: https://docs.rs/nitrokey +[examples]: https://docs.rs/crate/nitrokey/0.4.0/source/examples/ +[`nitrocli`]: https://github.com/d-e-s-o/nitrocli/tree/master/nitrocli [Nitrokey udev rules]: https://www.nitrokey.com/documentation/frequently-asked-questions-faq#openpgp-card-not-available [`libnitrokey`]: https://github.com/nitrokey/libnitrokey [`nitrokey-test`]: https://github.com/d-e-s-o/nitrokey-test -- cgit v1.2.3 From 43664ca3c449008e2859feb94e3142db3fa98625 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 14 Jan 2020 17:45:06 +0100 Subject: Release v0.5.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This release adds support for these Nitrokey commands: For all devices: - Listing the connected devices and connecting to one of them (list_devices and Manager::connect_path). - Querying the status of a Nitrokey device (Device::get_status). For the Nitrokey Storage: - Getting the SD card usage (Storage::get_sd_card_usage). - Getting the status of a background operation (Storage::get_operation_status). - Filling the SD card with random data (Storage::fill_sd_card). With this release, we support all functions provided by libnitrokey – except those listed as unsupported in the readme file. Therefore we should be able to release a stable version 1.0.0 soon. --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06836d9..54ee7d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ Copyright (C) 2019-2020 Robin Krahl SPDX-License-Identifier: CC0-1.0 --> -# Unreleased +# v0.5.0 (2020-01-14) - List these libnitrokey functions as unsupported: - `NK_change_firmware_password_pro` - `NK_connect_with_ID` diff --git a/Cargo.toml b/Cargo.toml index b57591b..727d47c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nitrokey" -version = "0.4.0" +version = "0.5.0" authors = ["Robin Krahl "] edition = "2018" homepage = "https://code.ireas.org/nitrokey-rs/" -- cgit v1.2.3