From 1e07212370806f3ea4ff7a6f66e5716b60ab1f77 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 14 Jan 2020 19:21:26 +0100 Subject: Bump nitrokey dependency to version 0.5.1 This change updates the version of the nitrokey crate that we use to 0.5.1. As part of that, it replaces occurrences of Storage::get_status with Storage::get_storage_status as the method has been renamed. Import subrepo nitrokey/:nitrokey at 817409140a8778215d2d65d614d3672166fff576 --- nitrokey/src/device/mod.rs | 238 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 221 insertions(+), 17 deletions(-) (limited to 'nitrokey/src/device/mod.rs') diff --git a/nitrokey/src/device/mod.rs b/nitrokey/src/device/mod.rs index 5e15f08..0234bf0 100644 --- a/nitrokey/src/device/mod.rs +++ b/nitrokey/src/device/mod.rs @@ -5,6 +5,8 @@ mod pro; mod storage; mod wrapper; +use std::convert::{TryFrom, TryInto}; +use std::ffi; use std::fmt; use libc; @@ -16,12 +18,14 @@ 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; pub use storage::{ - SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus, + OperationStatus, SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode, + VolumeStatus, }; pub use wrapper::DeviceWrapper; @@ -43,6 +47,114 @@ 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), + } + } +} + +/// 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, it uses the last +/// eight characters. If these are all zero, the first eight characters are used instead. This +/// function also makes sure that the returned string is lowercase, consistent with libnitrokey’s +/// hex string formatting. +/// +/// The reason for this behavior is that the Nitrokey Storage does not report its serial number at +/// all (all zero value), while the Nitrokey Pro with firmware 0.9 or later writes its serial +/// number to the last eight characters. Nitrokey Pro devices with firmware 0.8 or earlier wrote +/// their serial number to the first eight characters. +fn get_hidapi_serial_number(serial_number: &str) -> Option { + let len = serial_number.len(); + if len < 8 { + // The serial number in the USB descriptor has 12 bytes, we need at least four of them + return None; + } + + let iter = serial_number.char_indices().rev(); + let first_non_null = iter.skip_while(|(_, c)| *c == '0').next(); + if let Some((i, _)) = first_non_null { + if len - i < 8 { + // The last eight characters contain at least one non-zero character --> use them + let mut serial_number = serial_number.split_at(len - 8).1.to_string(); + serial_number.make_ascii_lowercase(); + Some(serial_number) + } else { + // The last eight characters are all zero --> use the first eight + let mut serial_number = serial_number.split_at(8).0.to_string(); + serial_number.make_ascii_lowercase(); + Some(serial_number) + } + } else { + // The serial number is all zero + None + } +} + /// A firmware version for a Nitrokey device. #[derive(Clone, Copy, Debug, PartialEq)] pub struct FirmwareVersion { @@ -58,6 +170,36 @@ 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, + /// 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 @@ -102,6 +244,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 + /// [`get_storage_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>(()) + /// ``` + /// + /// [`get_storage_status`]: struct.Storage.html#method.get_storage_status + /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected + /// [`Status`]: struct.Status.html + /// [`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. /// @@ -428,12 +599,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 +616,53 @@ 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 } +} + +#[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!(None, 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("00001234".to_string()), + get_hidapi_serial_number("100000001234") + ); + assert_eq!( + Some("12340000".to_string()), + get_hidapi_serial_number("123400000000") + ); + assert_eq!( + Some("00005678".to_string()), + get_hidapi_serial_number("000000000000000000005678") + ); + assert_eq!( + Some("00001234".to_string()), + get_hidapi_serial_number("000012340000000000000000") + ); + assert_eq!( + Some("0000ffff".to_string()), + get_hidapi_serial_number("00000000000000000000FFFF") + ); + assert_eq!( + Some("0000ffff".to_string()), + get_hidapi_serial_number("00000000000000000000ffff") + ); + } } -- cgit v1.2.1