aboutsummaryrefslogtreecommitdiff
path: root/nitrokey/src/device
diff options
context:
space:
mode:
authorRobin Krahl <robin.krahl@ireas.org>2020-01-29 12:25:33 +0100
committerDaniel Mueller <deso@posteo.net>2020-02-03 09:40:32 -0800
commit51d0fbb73eb42325fb2a0832810fd9e1d4339743 (patch)
tree766cfda4a1a9e47ac6cef5f558b3dc93c8372eeb /nitrokey/src/device
parent3dd4b7795f9a9a4285fe6add70a578e3a84bb59f (diff)
downloadnitrocli-51d0fbb73eb42325fb2a0832810fd9e1d4339743.tar.gz
nitrocli-51d0fbb73eb42325fb2a0832810fd9e1d4339743.tar.bz2
Update nitrokey dependency to 0.6.0
nitrokey 0.6.0 introduced the SerialNumber struct (instead of representing serial numbers as strings). We no longer have to manually format the serial number as SerialNumber implements Display. Import subrepo nitrokey/:nitrokey at 2a8ce725407f32db5ad61c37475719737c9b5c9c
Diffstat (limited to 'nitrokey/src/device')
-rw-r--r--nitrokey/src/device/mod.rs284
-rw-r--r--nitrokey/src/device/storage.rs14
2 files changed, 211 insertions, 87 deletions
diff --git a/nitrokey/src/device/mod.rs b/nitrokey/src/device/mod.rs
index 0234bf0..067fdf6 100644
--- a/nitrokey/src/device/mod.rs
+++ b/nitrokey/src/device/mod.rs
@@ -8,18 +8,17 @@ mod wrapper;
use std::convert::{TryFrom, TryInto};
use std::ffi;
use std::fmt;
+use std::str;
-use libc;
use nitrokey_sys;
use crate::auth::Authenticate;
use crate::config::{Config, RawConfig};
-use crate::error::{CommunicationError, Error};
+use crate::error::{CommunicationError, Error, LibraryError};
use crate::otp::GenerateOtp;
use crate::pws::GetPasswordSafe;
use crate::util::{
- get_command_result, get_cstring, get_last_error, owned_str_from_ptr, result_from_string,
- result_or_error,
+ get_command_result, get_cstring, owned_str_from_ptr, result_or_error, run_with_string,
};
pub use pro::Pro;
@@ -71,6 +70,98 @@ impl TryFrom<nitrokey_sys::NK_device_model> for Model {
}
}
+/// Serial number of a Nitrokey device.
+///
+/// The serial number can be formatted as a string using the [`ToString`][] trait, and it can be
+/// parsed from a string using the [`FromStr`][] trait. It can also be represented as a 32-bit
+/// unsigned integer using [`as_u32`][]. This integer is the ID of the smartcard of the Nitrokey
+/// device.
+///
+/// Neither the format of the string representation nor the integer representation are guaranteed
+/// to stay the same for new firmware versions.
+///
+/// [`as_u32`]: #method.as_u32
+/// [`FromStr`]: #impl-FromStr
+/// [`ToString`]: #impl-ToString
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct SerialNumber {
+ value: u32,
+}
+
+impl SerialNumber {
+ /// Creates an emtpty serial number.
+ ///
+ /// This function can be used to create a placeholder value or to compare a `SerialNumber`
+ /// instance with an empty serial number.
+ pub fn empty() -> Self {
+ SerialNumber::new(0)
+ }
+
+ fn new(value: u32) -> Self {
+ SerialNumber { value }
+ }
+
+ /// Returns the integer reprensentation of this serial number.
+ ///
+ /// This integer currently is the ID of the smartcard of the Nitrokey device. Upcoming
+ /// firmware versions might change the meaning of this representation, or add additional
+ /// components to the serial number.
+ // To provide a stable API even if the internal representation of SerialNumber changes, we want
+ // to borrow SerialNumber instead of copying it even if it might be less efficient.
+ #[allow(clippy::trivially_copy_pass_by_ref)]
+ pub fn as_u32(&self) -> u32 {
+ self.value
+ }
+}
+
+impl fmt::Display for SerialNumber {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{:#010x}", self.value)
+ }
+}
+
+impl str::FromStr for SerialNumber {
+ type Err = Error;
+
+ /// Try to parse a serial number from a hex string.
+ ///
+ /// The input string must be a valid hex string. Optionally, it can include a `0x` prefix.
+ ///
+ /// # Errors
+ ///
+ /// - [`InvalidHexString`][] if the given string is not a valid hex string
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use std::convert::TryFrom;
+ /// use nitrokey::{DeviceInfo, Error, SerialNumber};
+ ///
+ /// fn find_device(serial_number: &str) -> Result<Option<DeviceInfo>, Error> {
+ /// let serial_number: SerialNumber = serial_number.parse()?;
+ /// Ok(nitrokey::list_devices()?
+ /// .into_iter()
+ /// .filter(|device| device.serial_number == Some(serial_number))
+ /// .next())
+ /// }
+ ///
+ /// ```
+ ///
+ /// [`InvalidHexString`]: enum.LibraryError.html#variant.InvalidHexString
+ fn from_str(s: &str) -> Result<SerialNumber, Error> {
+ // ignore leading 0x
+ let hex_string = if s.starts_with("0x") {
+ s.split_at(2).1
+ } else {
+ s
+ };
+
+ u32::from_str_radix(hex_string, 16)
+ .map(SerialNumber::new)
+ .map_err(|_| LibraryError::InvalidHexString.into())
+ }
+}
+
/// Connection information for a Nitrokey device.
#[derive(Clone, Debug, PartialEq)]
pub struct DeviceInfo {
@@ -78,9 +169,8 @@ pub struct DeviceInfo {
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>,
+ /// The serial number of the device, or `None` if the device does not expose its serial number.
+ pub serial_number: Option<SerialNumber>,
}
impl TryFrom<&nitrokey_sys::NK_device_info> for DeviceInfo {
@@ -110,45 +200,42 @@ impl fmt::Display for DeviceInfo {
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),
+ match self.serial_number {
+ Some(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.
+/// Parses a serial number returned by hidapi.
///
/// 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.
+/// eight characters. If these are all zero, the first eight characters are used instead. The
+/// selected substring is parse as a hex string and its integer value is returned from the
+/// function. If the string cannot be parsed, this function returns `None`.
///
/// 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<String> {
+fn get_hidapi_serial_number(serial_number: &str) -> Option<SerialNumber> {
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
+ // The serial number in the USB descriptor has 12 bytes, we need at least four
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 {
+ let substr = 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)
+ serial_number.split_at(len - 8).1
} 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)
- }
+ serial_number.split_at(8).0
+ };
+ substr.parse().ok()
} else {
// The serial number is all zero
None
@@ -176,7 +263,7 @@ pub struct Status {
/// The firmware version of the device.
pub firmware_version: FirmwareVersion,
/// The serial number of the device.
- pub serial_number: u32,
+ pub serial_number: SerialNumber,
/// The configuration of the device.
pub config: Config,
}
@@ -188,14 +275,8 @@ impl From<nitrokey_sys::NK_status> for Status {
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(),
+ serial_number: SerialNumber::new(status.serial_number_smart_card),
+ config: RawConfig::from(&status).into(),
}
}
}
@@ -263,7 +344,7 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
/// let device = manager.connect()?;
/// let status = device.get_status()?;
/// println!("Firmware version: {}", status.firmware_version);
- /// println!("Serial number: {:x}", status.serial_number);
+ /// println!("Serial number: {}", status.serial_number);
/// # Ok::<(), nitrokey::Error>(())
/// ```
///
@@ -273,8 +354,9 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
/// [`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.
+ /// Returns the serial number of the Nitrokey device.
+ ///
+ /// For display purpuses, the serial number should be formatted as an 8-digit hex string.
///
/// # Example
///
@@ -292,8 +374,10 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
/// # Ok(())
/// # }
/// ```
- fn get_serial_number(&self) -> Result<String, Error> {
- result_from_string(unsafe { nitrokey_sys::NK_device_serial_number() })
+ fn get_serial_number(&self) -> Result<SerialNumber, Error> {
+ run_with_string(unsafe { nitrokey_sys::NK_device_serial_number() }, |s| {
+ s.parse()
+ })
}
/// Returns the number of remaining authentication attempts for the user. The total number of
@@ -388,14 +472,17 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
/// # }
/// ```
fn get_config(&self) -> Result<Config, Error> {
- let config_ptr = unsafe { nitrokey_sys::NK_read_config() };
- if config_ptr.is_null() {
- return Err(get_last_error());
- }
- let config_array_ptr = config_ptr as *const [u8; 5];
- let raw_config = unsafe { RawConfig::from(*config_array_ptr) };
- unsafe { libc::free(config_ptr as *mut libc::c_void) };
- Ok(raw_config.into())
+ 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(RawConfig::from(&raw_status).into())
}
/// Changes the administrator PIN.
@@ -625,44 +712,77 @@ pub(crate) fn connect_enum(model: Model) -> bool {
#[cfg(test)]
mod tests {
- use super::get_hidapi_serial_number;
+ use std::str::FromStr;
+
+ use super::{get_hidapi_serial_number, LibraryError, SerialNumber};
#[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")
- );
+ fn test_serial_number_display() {
+ fn assert_str(s: &str, n: u32) {
+ assert_eq!(s.to_owned(), SerialNumber::new(n).to_string());
+ }
+
+ assert_str("0x00000000", 0);
+ assert_str("0x00001000", 0x1000);
+ assert_str("0x12345678", 0x12345678);
+ }
+
+ #[test]
+ fn test_serial_number_try_from() {
+ fn assert_ok(v: u32, s: &str) {
+ assert_eq!(SerialNumber::new(v), SerialNumber::from_str(s).unwrap());
+ assert_eq!(
+ SerialNumber::new(v),
+ SerialNumber::from_str(format!("0x{}", s).as_ref()).unwrap()
+ );
+ }
+
+ fn assert_err(s: &str) {
+ match SerialNumber::from_str(s).unwrap_err() {
+ super::Error::LibraryError(LibraryError::InvalidHexString) => {}
+ err => assert!(
+ false,
+ "expected InvalidHexString error, got {} (input {})",
+ err, s
+ ),
+ }
+ }
+
+ assert_ok(0x1234, "1234");
+ assert_ok(0x1234, "01234");
+ assert_ok(0x1234, "001234");
+ assert_ok(0x1234, "0001234");
+
+ assert_ok(0, "0");
+ assert_ok(0xdeadbeef, "deadbeef");
+
+ assert_err("deadpork");
+ assert_err("blubb");
+ assert_err("");
+ }
+
+ #[test]
+ fn test_get_hidapi_serial_number() {
+ fn assert_none(s: &str) {
+ assert_eq!(None, get_hidapi_serial_number(s));
+ }
+
+ fn assert_some(n: u32, s: &str) {
+ assert_eq!(Some(SerialNumber::new(n)), get_hidapi_serial_number(s));
+ }
+
+ assert_none("");
+ assert_none("00000000000000000");
+ assert_none("blubb");
+ assert_none("1234");
+
+ assert_some(0x1234, "00001234");
+ assert_some(0x1234, "000000001234");
+ assert_some(0x1234, "100000001234");
+ assert_some(0x12340000, "123400000000");
+ assert_some(0x5678, "000000000000000000005678");
+ assert_some(0x1234, "000012340000000000000000");
+ assert_some(0xffff, "00000000000000000000FFFF");
+ assert_some(0xffff, "00000000000000000000ffff");
}
}
diff --git a/nitrokey/src/device/storage.rs b/nitrokey/src/device/storage.rs
index deb2844..5669a91 100644
--- a/nitrokey/src/device/storage.rs
+++ b/nitrokey/src/device/storage.rs
@@ -7,7 +7,7 @@ use std::ops;
use nitrokey_sys;
-use crate::device::{Device, FirmwareVersion, Model, Status};
+use crate::device::{Device, FirmwareVersion, Model, SerialNumber, Status};
use crate::error::{CommandError, Error};
use crate::otp::GenerateOtp;
use crate::util::{get_command_result, get_cstring, get_last_error};
@@ -678,7 +678,7 @@ impl<'a> Storage<'a> {
if usage_data.write_level_min > usage_data.write_level_max
|| usage_data.write_level_max > 100
{
- Err(Error::UnexpectedError)
+ Err(Error::UnexpectedError("Invalid write levels".to_owned()))
} else {
Ok(ops::Range {
start: usage_data.write_level_min,
@@ -708,10 +708,14 @@ impl<'a> Storage<'a> {
match status {
0..=100 => u8::try_from(status)
.map(OperationStatus::Ongoing)
- .map_err(|_| Error::UnexpectedError),
+ .map_err(|_| {
+ Error::UnexpectedError("Cannot create u8 from operation status".to_owned())
+ }),
-1 => Ok(OperationStatus::Idle),
-2 => Err(get_last_error()),
- _ => Err(Error::UnexpectedError),
+ _ => Err(Error::UnexpectedError(
+ "Invalid operation status".to_owned(),
+ )),
}
}
@@ -823,7 +827,7 @@ impl<'a> Device<'a> for Storage<'a> {
let storage_status = self.get_storage_status()?;
status.firmware_version = storage_status.firmware_version;
- status.serial_number = storage_status.serial_number_smart_card;
+ status.serial_number = SerialNumber::new(storage_status.serial_number_smart_card);
Ok(status)
}