aboutsummaryrefslogtreecommitdiff
path: root/src/device/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/device/mod.rs')
-rw-r--r--src/device/mod.rs209
1 files changed, 192 insertions, 17 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")
+ );
+ }
}