diff options
| author | Robin Krahl <robin.krahl@ireas.org> | 2020-01-14 18:11:02 +0100 | 
|---|---|---|
| committer | Robin Krahl <robin.krahl@ireas.org> | 2020-01-14 18:11:02 +0100 | 
| commit | 62d37c8d4e7d1caca21f23978198b721efc5973b (patch) | |
| tree | cae2b5f81bb1245caef37cba80f934878978878c /src/device | |
| parent | 528e56a0ff759ea81b61eea368cf53b5540dc89a (diff) | |
| parent | 43664ca3c449008e2859feb94e3142db3fa98625 (diff) | |
| download | nitrokey-rs-62d37c8d4e7d1caca21f23978198b721efc5973b.tar.gz nitrokey-rs-62d37c8d4e7d1caca21f23978198b721efc5973b.tar.bz2 | |
Merge branch 'release-0.5.0'
Diffstat (limited to 'src/device')
| -rw-r--r-- | src/device/mod.rs | 209 | ||||
| -rw-r--r-- | src/device/pro.rs | 18 | ||||
| -rw-r--r-- | src/device/storage.rs | 161 | ||||
| -rw-r--r-- | src/device/wrapper.rs | 9 | 
4 files changed, 372 insertions, 25 deletions
| diff --git a/src/device/mod.rs b/src/device/mod.rs index 5e15f08..16d6b11 100644 --- a/src/device/mod.rs +++ b/src/device/mod.rs @@ -5,6 +5,9 @@ mod pro;  mod storage;  mod wrapper; +use std::cmp; +use std::convert::{TryFrom, TryInto}; +use std::ffi;  use std::fmt;  use libc; @@ -16,12 +19,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 +48,97 @@ impl fmt::Display for Model {      }  } +impl From<Model> 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<nitrokey_sys::NK_device_model> for Model { +    type Error = Error; + +    fn try_from(model: nitrokey_sys::NK_device_model) -> Result<Self, Error> { +        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<Model>, +    /// 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<String>, +} + +impl TryFrom<&nitrokey_sys::NK_device_info> for DeviceInfo { +    type Error = Error; + +    fn try_from(device_info: &nitrokey_sys::NK_device_info) -> Result<DeviceInfo, Error> { +        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<String> { +    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 { @@ -58,6 +154,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<nitrokey_sys::NK_status> 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 +228,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<Status, Error>; +      /// Returns the serial number of the Nitrokey device.  The serial number is the string      /// representation of a hex number.      /// @@ -428,12 +583,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt      }  } -fn get_connected_model() -> Option<Model> { -    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, Error> { +    Model::try_from(unsafe { nitrokey_sys::NK_get_device_model() })  }  pub(crate) fn create_device_wrapper( @@ -449,16 +600,40 @@ pub(crate) fn create_device_wrapper(  pub(crate) fn get_connected_device(      manager: &mut crate::Manager,  ) -> Result<DeviceWrapper<'_>, 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!( +            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/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<Status, Error> { +        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..deb2844 100644 --- a/src/device/storage.rs +++ b/src/device/storage.rs @@ -1,14 +1,16 @@ -// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org> +// Copyright (C) 2019-2020 Robin Krahl <robin.krahl@ireas.org>  // SPDX-License-Identifier: MIT +use std::convert::TryFrom as _;  use std::fmt; +use std::ops;  use nitrokey_sys; -use crate::device::{Device, FirmwareVersion, Model}; -use crate::error::Error; +use crate::device::{Device, FirmwareVersion, Model, Status}; +use crate::error::{CommandError, 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.  /// @@ -141,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 { @@ -525,7 +541,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 +550,7 @@ impl<'a> Storage<'a> {      /// #     Ok(())      /// # }      /// ``` -    pub fn get_status(&self) -> Result<StorageStatus, Error> { +    pub fn get_storage_status(&self) -> Result<StorageStatus, Error> {          let mut raw_status = nitrokey_sys::NK_storage_status {              unencrypted_volume_read_only: false,              unencrypted_volume_active: false, @@ -635,11 +651,117 @@ 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<ops::Range<u8>, 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() })      } +    /// 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. +    /// +    /// [`fill_sd_card`]: #method.fill_sd_card +    pub fn get_operation_status(&self) -> Result<OperationStatus, Error> { +        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), +        } +    } + +    /// 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 @@ -678,6 +800,33 @@ impl<'a> Device<'a> for Storage<'a> {      fn get_model(&self) -> Model {          Model::Storage      } + +    fn get_status(&self) -> Result<Status, Error> { +        // 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 (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 + +        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 = self.get_storage_status()?; +        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..69291ad 100644 --- a/src/device/wrapper.rs +++ b/src/device/wrapper.rs @@ -1,7 +1,7 @@  // Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>  // 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<Status, Error> { +        match self { +            DeviceWrapper::Pro(dev) => dev.get_status(), +            DeviceWrapper::Storage(dev) => dev.get_status(), +        } +    }  } | 
