diff options
Diffstat (limited to 'src/device')
| -rw-r--r-- | src/device/mod.rs | 147 | 
1 files changed, 131 insertions, 16 deletions
| diff --git a/src/device/mod.rs b/src/device/mod.rs index 5e15f08..e015886 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,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; @@ -43,6 +47,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 { @@ -428,12 +523,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 +540,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") +        ); +    }  } | 
