aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/auth.rs51
-rw-r--r--src/config.rs68
-rw-r--r--src/device/mod.rs792
-rw-r--r--src/device/pro.rs93
-rw-r--r--src/device/storage.rs (renamed from src/device.rs)842
-rw-r--r--src/device/wrapper.rs142
-rw-r--r--src/error.rs63
-rw-r--r--src/lib.rs167
-rw-r--r--src/otp.rs10
-rw-r--r--src/pws.rs3
-rw-r--r--src/util.rs66
11 files changed, 1481 insertions, 816 deletions
diff --git a/src/auth.rs b/src/auth.rs
index 0b000f7..5ca59da 100644
--- a/src/auth.rs
+++ b/src/auth.rs
@@ -1,13 +1,13 @@
// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT
+use std::convert::TryFrom as _;
+use std::ffi::CString;
use std::marker;
use std::ops;
use std::os::raw::c_char;
use std::os::raw::c_int;
-use nitrokey_sys;
-
use crate::config::{Config, RawConfig};
use crate::device::{Device, DeviceWrapper, Pro, Storage};
use crate::error::Error;
@@ -116,9 +116,7 @@ pub trait Authenticate<'a> {
}
trait AuthenticatedDevice<T> {
- fn new(device: T, temp_password: Vec<u8>) -> Self;
-
- fn temp_password_ptr(&self) -> *const c_char;
+ fn new(device: T, temp_password: CString) -> Self;
}
/// A Nitrokey device with user authentication.
@@ -128,12 +126,12 @@ trait AuthenticatedDevice<T> {
/// method.
///
/// [`Authenticate`]: trait.Authenticate.html
-/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
+/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user
/// [`device`]: #method.device
#[derive(Debug)]
pub struct User<'a, T: Device<'a>> {
device: T,
- temp_password: Vec<u8>,
+ temp_password: CString,
marker: marker::PhantomData<&'a T>,
}
@@ -149,7 +147,7 @@ pub struct User<'a, T: Device<'a>> {
#[derive(Debug)]
pub struct Admin<'a, T: Device<'a>> {
device: T,
- temp_password: Vec<u8>,
+ temp_password: CString,
marker: marker::PhantomData<&'a T>,
}
@@ -168,7 +166,7 @@ where
Err(err) => return Err((device, err)),
};
let password_ptr = password.as_ptr();
- let temp_password_ptr = temp_password.as_ptr() as *const c_char;
+ let temp_password_ptr = temp_password.as_ptr();
match callback(password_ptr, temp_password_ptr) {
0 => Ok(A::new(device, temp_password)),
rv => Err((device, Error::from(rv))),
@@ -233,29 +231,25 @@ impl<'a, T: Device<'a>> ops::DerefMut for User<'a, T> {
impl<'a, T: Device<'a>> GenerateOtp for User<'a, T> {
fn get_hotp_code(&mut self, slot: u8) -> Result<String, Error> {
result_from_string(unsafe {
- nitrokey_sys::NK_get_hotp_code_PIN(slot, self.temp_password_ptr())
+ nitrokey_sys::NK_get_hotp_code_PIN(slot, self.temp_password.as_ptr())
})
}
fn get_totp_code(&self, slot: u8) -> Result<String, Error> {
result_from_string(unsafe {
- nitrokey_sys::NK_get_totp_code_PIN(slot, 0, 0, 0, self.temp_password_ptr())
+ nitrokey_sys::NK_get_totp_code_PIN(slot, 0, 0, 0, self.temp_password.as_ptr())
})
}
}
impl<'a, T: Device<'a>> AuthenticatedDevice<T> for User<'a, T> {
- fn new(device: T, temp_password: Vec<u8>) -> Self {
+ fn new(device: T, temp_password: CString) -> Self {
User {
device,
temp_password,
marker: marker::PhantomData,
}
}
-
- fn temp_password_ptr(&self) -> *const c_char {
- self.temp_password.as_ptr() as *const c_char
- }
}
impl<'a, T: Device<'a>> ops::Deref for Admin<'a, T> {
@@ -284,7 +278,8 @@ impl<'a, T: Device<'a>> Admin<'a, T> {
///
/// # Errors
///
- /// - [`InvalidSlot`][] if the provided numlock, capslock or scrolllock slot is larger than two
+ /// - [`InvalidSlot`][] if the provided Num Lock, Caps Lock or Scroll Lock slot is larger than
+ /// two
///
/// # Example
///
@@ -312,12 +307,12 @@ impl<'a, T: Device<'a>> Admin<'a, T> {
let raw_config = RawConfig::try_from(config)?;
get_command_result(unsafe {
nitrokey_sys::NK_write_config(
- raw_config.numlock,
- raw_config.capslock,
- raw_config.scrollock,
+ raw_config.num_lock,
+ raw_config.caps_lock,
+ raw_config.scroll_lock,
raw_config.user_password,
false,
- self.temp_password_ptr(),
+ self.temp_password.as_ptr(),
)
})
}
@@ -336,7 +331,7 @@ impl<'a, T: Device<'a>> ConfigureOtp for Admin<'a, T> {
raw_data.use_enter,
raw_data.use_token_id,
raw_data.token_id.as_ptr(),
- self.temp_password_ptr(),
+ self.temp_password.as_ptr(),
)
})
}
@@ -353,36 +348,32 @@ impl<'a, T: Device<'a>> ConfigureOtp for Admin<'a, T> {
raw_data.use_enter,
raw_data.use_token_id,
raw_data.token_id.as_ptr(),
- self.temp_password_ptr(),
+ self.temp_password.as_ptr(),
)
})
}
fn erase_hotp_slot(&mut self, slot: u8) -> Result<(), Error> {
get_command_result(unsafe {
- nitrokey_sys::NK_erase_hotp_slot(slot, self.temp_password_ptr())
+ nitrokey_sys::NK_erase_hotp_slot(slot, self.temp_password.as_ptr())
})
}
fn erase_totp_slot(&mut self, slot: u8) -> Result<(), Error> {
get_command_result(unsafe {
- nitrokey_sys::NK_erase_totp_slot(slot, self.temp_password_ptr())
+ nitrokey_sys::NK_erase_totp_slot(slot, self.temp_password.as_ptr())
})
}
}
impl<'a, T: Device<'a>> AuthenticatedDevice<T> for Admin<'a, T> {
- fn new(device: T, temp_password: Vec<u8>) -> Self {
+ fn new(device: T, temp_password: CString) -> Self {
Admin {
device,
temp_password,
marker: marker::PhantomData,
}
}
-
- fn temp_password_ptr(&self) -> *const c_char {
- self.temp_password.as_ptr() as *const c_char
- }
}
impl<'a> Authenticate<'a> for DeviceWrapper<'a> {
diff --git a/src/config.rs b/src/config.rs
index c273792..bc935d7 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,20 +1,22 @@
// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT
+use std::convert;
+
use crate::error::{Error, LibraryError};
/// The configuration for a Nitrokey.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Config {
- /// If set, the stick will generate a code from the HOTP slot with the given number if numlock
- /// is pressed. The slot number must be 0, 1 or 2.
- pub numlock: Option<u8>,
- /// If set, the stick will generate a code from the HOTP slot with the given number if capslock
+ /// If set, the stick will generate a code from the HOTP slot with the given number if Num Lock
/// is pressed. The slot number must be 0, 1 or 2.
- pub capslock: Option<u8>,
- /// If set, the stick will generate a code from the HOTP slot with the given number if
- /// scrollock is pressed. The slot number must be 0, 1 or 2.
- pub scrollock: Option<u8>,
+ pub num_lock: Option<u8>,
+ /// If set, the stick will generate a code from the HOTP slot with the given number if Caps
+ /// Lock is pressed. The slot number must be 0, 1 or 2.
+ pub caps_lock: Option<u8>,
+ /// If set, the stick will generate a code from the HOTP slot with the given number if Scroll
+ /// Lock is pressed. The slot number must be 0, 1 or 2.
+ pub scroll_lock: Option<u8>,
/// If set, OTP generation using [`get_hotp_code`][] or [`get_totp_code`][] requires user
/// authentication. Otherwise, OTPs can be generated without authentication.
///
@@ -25,9 +27,9 @@ pub struct Config {
#[derive(Debug)]
pub struct RawConfig {
- pub numlock: u8,
- pub capslock: u8,
- pub scrollock: u8,
+ pub num_lock: u8,
+ pub caps_lock: u8,
+ pub scroll_lock: u8,
pub user_password: bool,
}
@@ -54,38 +56,40 @@ fn option_to_config_otp_slot(value: Option<u8>) -> Result<u8, Error> {
impl Config {
/// Constructs a new instance of this struct.
pub fn new(
- numlock: Option<u8>,
- capslock: Option<u8>,
- scrollock: Option<u8>,
+ num_lock: Option<u8>,
+ caps_lock: Option<u8>,
+ scroll_lock: Option<u8>,
user_password: bool,
) -> Config {
Config {
- numlock,
- capslock,
- scrollock,
+ num_lock,
+ caps_lock,
+ scroll_lock,
user_password,
}
}
}
-impl RawConfig {
- pub fn try_from(config: Config) -> Result<RawConfig, Error> {
+impl convert::TryFrom<Config> for RawConfig {
+ type Error = Error;
+
+ fn try_from(config: Config) -> Result<RawConfig, Error> {
Ok(RawConfig {
- numlock: option_to_config_otp_slot(config.numlock)?,
- capslock: option_to_config_otp_slot(config.capslock)?,
- scrollock: option_to_config_otp_slot(config.scrollock)?,
+ num_lock: option_to_config_otp_slot(config.num_lock)?,
+ caps_lock: option_to_config_otp_slot(config.caps_lock)?,
+ scroll_lock: option_to_config_otp_slot(config.scroll_lock)?,
user_password: config.user_password,
})
}
}
-impl From<[u8; 5]> for RawConfig {
- fn from(data: [u8; 5]) -> Self {
- RawConfig {
- numlock: data[0],
- capslock: data[1],
- scrollock: data[2],
- user_password: data[3] != 0,
+impl From<&nitrokey_sys::NK_status> for RawConfig {
+ fn from(status: &nitrokey_sys::NK_status) -> Self {
+ Self {
+ num_lock: status.config_numlock,
+ caps_lock: status.config_capslock,
+ scroll_lock: status.config_scrolllock,
+ user_password: status.otp_user_password,
}
}
}
@@ -93,9 +97,9 @@ impl From<[u8; 5]> for RawConfig {
impl Into<Config> for RawConfig {
fn into(self) -> Config {
Config {
- numlock: config_otp_slot_to_option(self.numlock),
- capslock: config_otp_slot_to_option(self.capslock),
- scrollock: config_otp_slot_to_option(self.scrollock),
+ num_lock: config_otp_slot_to_option(self.num_lock),
+ caps_lock: config_otp_slot_to_option(self.caps_lock),
+ scroll_lock: config_otp_slot_to_option(self.scroll_lock),
user_password: self.user_password,
}
}
diff --git a/src/device/mod.rs b/src/device/mod.rs
new file mode 100644
index 0000000..7fec18b
--- /dev/null
+++ b/src/device/mod.rs
@@ -0,0 +1,792 @@
+// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
+// SPDX-License-Identifier: MIT
+
+mod pro;
+mod storage;
+mod wrapper;
+
+use std::convert::{TryFrom, TryInto};
+use std::ffi;
+use std::fmt;
+use std::str;
+
+use crate::auth::Authenticate;
+use crate::config::{Config, RawConfig};
+use crate::error::{CommunicationError, Error, LibraryError};
+use crate::otp::GenerateOtp;
+use crate::pws::GetPasswordSafe;
+use crate::util::{
+ get_command_result, get_cstring, owned_str_from_ptr, result_or_error, run_with_string,
+};
+
+pub use pro::Pro;
+pub use storage::{
+ OperationStatus, SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode,
+ VolumeStatus,
+};
+pub use wrapper::DeviceWrapper;
+
+/// Available Nitrokey models.
+#[derive(Clone, Copy, Debug, PartialEq)]
+#[non_exhaustive]
+pub enum Model {
+ /// The Nitrokey Storage.
+ Storage,
+ /// The Nitrokey Pro.
+ Pro,
+}
+
+impl fmt::Display for Model {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str(match *self {
+ Model::Pro => "Pro",
+ Model::Storage => "Storage",
+ })
+ }
+}
+
+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),
+ }
+ }
+}
+
+/// 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 {
+ /// 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 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 {
+ 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(serial_number) => write!(f, "serial no. {}", serial_number),
+ None => write!(f, "an unknown serial number"),
+ }
+ }
+}
+
+/// 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. 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<SerialNumber> {
+ let len = serial_number.len();
+ if len < 8 {
+ // The serial number in the USB descriptor has 12 bytes, we need at least four
+ return None;
+ }
+
+ let mut iter = serial_number.char_indices().rev();
+ if let Some((i, _)) = iter.find(|(_, c)| *c != '0') {
+ let substr = if len - i < 8 {
+ // The last eight characters contain at least one non-zero character --> use them
+ serial_number.split_at(len - 8).1
+ } else {
+ // The last eight characters are all zero --> use the first eight
+ serial_number.split_at(8).0
+ };
+ substr.parse().ok()
+ } else {
+ // The serial number is all zero
+ None
+ }
+}
+
+/// A firmware version for a Nitrokey device.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct FirmwareVersion {
+ /// The major firmware version, e. g. 0 in v0.40.
+ pub major: u8,
+ /// The minor firmware version, e. g. 40 in v0.40.
+ pub minor: u8,
+}
+
+impl fmt::Display for FirmwareVersion {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "v{}.{}", self.major, self.minor)
+ }
+}
+
+/// 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: SerialNumber,
+ /// 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: SerialNumber::new(status.serial_number_smart_card),
+ config: RawConfig::from(&status).into(),
+ }
+ }
+}
+
+/// A Nitrokey device.
+///
+/// This trait provides the commands that can be executed without authentication and that are
+/// present on all supported Nitrokey devices.
+pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt::Debug {
+ /// Returns the [`Manager`][] instance that has been used to connect to this device.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use nitrokey::{Device, DeviceWrapper};
+ ///
+ /// fn do_something(device: DeviceWrapper) {
+ /// // reconnect to any device
+ /// let manager = device.into_manager();
+ /// let device = manager.connect();
+ /// // do something with the device
+ /// // ...
+ /// }
+ ///
+ /// match nitrokey::take()?.connect() {
+ /// Ok(device) => do_something(device),
+ /// Err(err) => println!("Could not connect to a Nitrokey: {}", err),
+ /// }
+ /// # Ok::<(), nitrokey::Error>(())
+ /// ```
+ ///
+ /// [`Manager`]: struct.Manager.html
+ fn into_manager(self) -> &'a mut crate::Manager;
+
+ /// Returns the model of the connected Nitrokey device.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::Device;
+ /// # use nitrokey::Error;
+ ///
+ /// # fn try_main() -> Result<(), Error> {
+ /// let mut manager = nitrokey::take()?;
+ /// let device = manager.connect()?;
+ /// println!("Connected to a Nitrokey {}", device.get_model());
+ /// # Ok(())
+ /// # }
+ 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: {}", 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.
+ ///
+ /// For display purpuses, the serial number should be formatted as an 8-digit hex string.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::Device;
+ /// # use nitrokey::Error;
+ ///
+ /// # fn try_main() -> Result<(), Error> {
+ /// let mut manager = nitrokey::take()?;
+ /// let device = manager.connect()?;
+ /// match device.get_serial_number() {
+ /// Ok(number) => println!("serial no: {}", number),
+ /// Err(err) => eprintln!("Could not get serial number: {}", err),
+ /// };
+ /// # Ok(())
+ /// # }
+ /// ```
+ 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
+ /// available attempts is three.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::Device;
+ /// # use nitrokey::Error;
+ ///
+ /// # fn try_main() -> Result<(), Error> {
+ /// let mut manager = nitrokey::take()?;
+ /// let device = manager.connect()?;
+ /// let count = device.get_user_retry_count();
+ /// match device.get_user_retry_count() {
+ /// Ok(count) => println!("{} remaining authentication attempts (user)", count),
+ /// Err(err) => eprintln!("Could not get user retry count: {}", err),
+ /// }
+ /// # Ok(())
+ /// # }
+ /// ```
+ fn get_user_retry_count(&self) -> Result<u8, Error> {
+ result_or_error(unsafe { nitrokey_sys::NK_get_user_retry_count() })
+ }
+
+ /// Returns the number of remaining authentication attempts for the admin. The total number of
+ /// available attempts is three.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::Device;
+ /// # use nitrokey::Error;
+ ///
+ /// # fn try_main() -> Result<(), Error> {
+ /// let mut manager = nitrokey::take()?;
+ /// let device = manager.connect()?;
+ /// let count = device.get_admin_retry_count();
+ /// match device.get_admin_retry_count() {
+ /// Ok(count) => println!("{} remaining authentication attempts (admin)", count),
+ /// Err(err) => eprintln!("Could not get admin retry count: {}", err),
+ /// }
+ /// # Ok(())
+ /// # }
+ /// ```
+ fn get_admin_retry_count(&self) -> Result<u8, Error> {
+ result_or_error(unsafe { nitrokey_sys::NK_get_admin_retry_count() })
+ }
+
+ /// Returns the firmware version.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::Device;
+ /// # use nitrokey::Error;
+ ///
+ /// # fn try_main() -> Result<(), Error> {
+ /// let mut manager = nitrokey::take()?;
+ /// let device = manager.connect()?;
+ /// match device.get_firmware_version() {
+ /// Ok(version) => println!("Firmware version: {}", version),
+ /// Err(err) => eprintln!("Could not access firmware version: {}", err),
+ /// };
+ /// # Ok(())
+ /// # }
+ /// ```
+ fn get_firmware_version(&self) -> Result<FirmwareVersion, Error> {
+ let major = result_or_error(unsafe { nitrokey_sys::NK_get_major_firmware_version() })?;
+ let minor = result_or_error(unsafe { nitrokey_sys::NK_get_minor_firmware_version() })?;
+ Ok(FirmwareVersion { major, minor })
+ }
+
+ /// Returns the current configuration of the Nitrokey device.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::Device;
+ /// # use nitrokey::Error;
+ ///
+ /// # fn try_main() -> Result<(), Error> {
+ /// let mut manager = nitrokey::take()?;
+ /// let device = manager.connect()?;
+ /// let config = device.get_config()?;
+ /// println!("Num Lock binding: {:?}", config.num_lock);
+ /// println!("Caps Lock binding: {:?}", config.caps_lock);
+ /// println!("Scroll Lock binding: {:?}", config.scroll_lock);
+ /// println!("require password for OTP: {:?}", config.user_password);
+ /// # Ok(())
+ /// # }
+ /// ```
+ fn get_config(&self) -> Result<Config, 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(RawConfig::from(&raw_status).into())
+ }
+
+ /// Changes the administrator PIN.
+ ///
+ /// # Errors
+ ///
+ /// - [`InvalidString`][] if one of the provided passwords contains a null byte
+ /// - [`WrongPassword`][] if the current admin password is wrong
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::Device;
+ /// # use nitrokey::Error;
+ ///
+ /// # fn try_main() -> Result<(), Error> {
+ /// let mut manager = nitrokey::take()?;
+ /// let mut device = manager.connect()?;
+ /// match device.change_admin_pin("12345678", "12345679") {
+ /// Ok(()) => println!("Updated admin PIN."),
+ /// Err(err) => eprintln!("Failed to update admin PIN: {}", err),
+ /// };
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
+ /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
+ fn change_admin_pin(&mut self, current: &str, new: &str) -> Result<(), Error> {
+ let current_string = get_cstring(current)?;
+ let new_string = get_cstring(new)?;
+ get_command_result(unsafe {
+ nitrokey_sys::NK_change_admin_PIN(current_string.as_ptr(), new_string.as_ptr())
+ })
+ }
+
+ /// Changes the user PIN.
+ ///
+ /// # Errors
+ ///
+ /// - [`InvalidString`][] if one of the provided passwords contains a null byte
+ /// - [`WrongPassword`][] if the current user password is wrong
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::Device;
+ /// # use nitrokey::Error;
+ ///
+ /// # fn try_main() -> Result<(), Error> {
+ /// let mut manager = nitrokey::take()?;
+ /// let mut device = manager.connect()?;
+ /// match device.change_user_pin("123456", "123457") {
+ /// Ok(()) => println!("Updated admin PIN."),
+ /// Err(err) => eprintln!("Failed to update admin PIN: {}", err),
+ /// };
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
+ /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
+ fn change_user_pin(&mut self, current: &str, new: &str) -> Result<(), Error> {
+ let current_string = get_cstring(current)?;
+ let new_string = get_cstring(new)?;
+ get_command_result(unsafe {
+ nitrokey_sys::NK_change_user_PIN(current_string.as_ptr(), new_string.as_ptr())
+ })
+ }
+
+ /// Unlocks the user PIN after three failed login attempts and sets it to the given value.
+ ///
+ /// # Errors
+ ///
+ /// - [`InvalidString`][] if one of the provided passwords contains a null byte
+ /// - [`WrongPassword`][] if the admin password is wrong
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::Device;
+ /// # use nitrokey::Error;
+ ///
+ /// # fn try_main() -> Result<(), Error> {
+ /// let mut manager = nitrokey::take()?;
+ /// let mut device = manager.connect()?;
+ /// match device.unlock_user_pin("12345678", "123456") {
+ /// Ok(()) => println!("Unlocked user PIN."),
+ /// Err(err) => eprintln!("Failed to unlock user PIN: {}", err),
+ /// };
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
+ /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
+ fn unlock_user_pin(&mut self, admin_pin: &str, user_pin: &str) -> Result<(), Error> {
+ let admin_pin_string = get_cstring(admin_pin)?;
+ let user_pin_string = get_cstring(user_pin)?;
+ get_command_result(unsafe {
+ nitrokey_sys::NK_unlock_user_password(
+ admin_pin_string.as_ptr(),
+ user_pin_string.as_ptr(),
+ )
+ })
+ }
+
+ /// Locks the Nitrokey device.
+ ///
+ /// This disables the password store if it has been unlocked. On the Nitrokey Storage, this
+ /// also disables the volumes if they have been enabled.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::Device;
+ /// # use nitrokey::Error;
+ ///
+ /// # fn try_main() -> Result<(), Error> {
+ /// let mut manager = nitrokey::take()?;
+ /// let mut device = manager.connect()?;
+ /// match device.lock() {
+ /// Ok(()) => println!("Locked the Nitrokey device."),
+ /// Err(err) => eprintln!("Could not lock the Nitrokey device: {}", err),
+ /// };
+ /// # Ok(())
+ /// # }
+ /// ```
+ fn lock(&mut self) -> Result<(), Error> {
+ get_command_result(unsafe { nitrokey_sys::NK_lock_device() })
+ }
+
+ /// Performs a factory reset on the Nitrokey device.
+ ///
+ /// This commands performs a factory reset on the smart card (like the factory reset via `gpg
+ /// --card-edit`) and then clears the flash memory (password safe, one-time passwords etc.).
+ /// After a factory reset, [`build_aes_key`][] has to be called before the password safe or the
+ /// encrypted volume can be used.
+ ///
+ /// # Errors
+ ///
+ /// - [`InvalidString`][] if the provided password contains a null byte
+ /// - [`WrongPassword`][] if the admin password is wrong
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::Device;
+ /// # use nitrokey::Error;
+ ///
+ /// # fn try_main() -> Result<(), Error> {
+ /// let mut manager = nitrokey::take()?;
+ /// let mut device = manager.connect()?;
+ /// match device.factory_reset("12345678") {
+ /// Ok(()) => println!("Performed a factory reset."),
+ /// Err(err) => eprintln!("Could not perform a factory reset: {}", err),
+ /// };
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
+ /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
+ /// [`build_aes_key`]: #method.build_aes_key
+ fn factory_reset(&mut self, admin_pin: &str) -> Result<(), Error> {
+ let admin_pin_string = get_cstring(admin_pin)?;
+ get_command_result(unsafe { nitrokey_sys::NK_factory_reset(admin_pin_string.as_ptr()) })
+ }
+
+ /// Builds a new AES key on the Nitrokey.
+ ///
+ /// The AES key is used to encrypt the password safe and the encrypted volume. You may need
+ /// to call this method after a factory reset, either using [`factory_reset`][] or using `gpg
+ /// --card-edit`. You can also use it to destroy the data stored in the password safe or on
+ /// the encrypted volume.
+ ///
+ /// # Errors
+ ///
+ /// - [`InvalidString`][] if the provided password contains a null byte
+ /// - [`WrongPassword`][] if the admin password is wrong
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::Device;
+ /// # use nitrokey::Error;
+ ///
+ /// # fn try_main() -> Result<(), Error> {
+ /// let mut manager = nitrokey::take()?;
+ /// let mut device = manager.connect()?;
+ /// match device.build_aes_key("12345678") {
+ /// Ok(()) => println!("New AES keys have been built."),
+ /// Err(err) => eprintln!("Could not build new AES keys: {}", err),
+ /// };
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
+ /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
+ /// [`factory_reset`]: #method.factory_reset
+ fn build_aes_key(&mut self, admin_pin: &str) -> Result<(), Error> {
+ let admin_pin_string = get_cstring(admin_pin)?;
+ get_command_result(unsafe { nitrokey_sys::NK_build_aes_key(admin_pin_string.as_ptr()) })
+ }
+}
+
+fn get_connected_model() -> Result<Model, Error> {
+ Model::try_from(unsafe { nitrokey_sys::NK_get_device_model() })
+}
+
+pub(crate) fn create_device_wrapper(
+ manager: &mut crate::Manager,
+ model: Model,
+) -> DeviceWrapper<'_> {
+ match model {
+ Model::Pro => Pro::new(manager).into(),
+ Model::Storage => Storage::new(manager).into(),
+ }
+}
+
+pub(crate) fn get_connected_device(
+ manager: &mut crate::Manager,
+) -> Result<DeviceWrapper<'_>, Error> {
+ Ok(create_device_wrapper(manager, get_connected_model()?))
+}
+
+pub(crate) fn connect_enum(model: Model) -> bool {
+ unsafe { nitrokey_sys::NK_login_enum(model.into()) == 1 }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::str::FromStr;
+
+ use super::{get_hidapi_serial_number, LibraryError, SerialNumber};
+
+ #[test]
+ 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/src/device/pro.rs b/src/device/pro.rs
new file mode 100644
index 0000000..0d5443e
--- /dev/null
+++ b/src/device/pro.rs
@@ -0,0 +1,93 @@
+// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
+// SPDX-License-Identifier: MIT
+
+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.
+///
+/// Use the [`connect`][] method to obtain an instance wrapper or the [`connect_pro`] method to
+/// directly obtain an instance. If you want to execute a command that requires user or admin
+/// authentication, use [`authenticate_admin`][] or [`authenticate_user`][].
+///
+/// # Examples
+///
+/// Authentication with error handling:
+///
+/// ```no_run
+/// use nitrokey::{Authenticate, User, Pro};
+/// # use nitrokey::Error;
+///
+/// fn perform_user_task<'a>(device: &User<'a, Pro<'a>>) {}
+/// fn perform_other_task(device: &Pro) {}
+///
+/// # fn try_main() -> Result<(), Error> {
+/// let mut manager = nitrokey::take()?;
+/// let device = manager.connect_pro()?;
+/// let device = match device.authenticate_user("123456") {
+/// Ok(user) => {
+/// perform_user_task(&user);
+/// user.device()
+/// },
+/// Err((device, err)) => {
+/// eprintln!("Could not authenticate as user: {}", err);
+/// device
+/// },
+/// };
+/// perform_other_task(&device);
+/// # Ok(())
+/// # }
+/// ```
+///
+/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
+/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user
+/// [`connect`]: struct.Manager.html#method.connect
+/// [`connect_pro`]: struct.Manager.html#method.connect_pro
+#[derive(Debug)]
+pub struct Pro<'a> {
+ manager: Option<&'a mut crate::Manager>,
+}
+
+impl<'a> Pro<'a> {
+ pub(crate) fn new(manager: &'a mut crate::Manager) -> Pro<'a> {
+ Pro {
+ manager: Some(manager),
+ }
+ }
+}
+
+impl<'a> Drop for Pro<'a> {
+ fn drop(&mut self) {
+ unsafe {
+ nitrokey_sys::NK_logout();
+ }
+ }
+}
+
+impl<'a> Device<'a> for Pro<'a> {
+ fn into_manager(mut self) -> &'a mut crate::Manager {
+ self.manager.take().unwrap()
+ }
+
+ 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.rs b/src/device/storage.rs
index 758d4c1..a18d94f 100644
--- a/src/device.rs
+++ b/src/device/storage.rs
@@ -1,165 +1,14 @@
-// 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 libc;
-use nitrokey_sys;
-
-use crate::auth::Authenticate;
-use crate::config::{Config, RawConfig};
-use crate::error::{CommunicationError, Error};
+use crate::device::{Device, FirmwareVersion, Model, SerialNumber, Status};
+use crate::error::{CommandError, 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,
-};
-
-/// Available Nitrokey models.
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub enum Model {
- /// The Nitrokey Storage.
- Storage,
- /// The Nitrokey Pro.
- Pro,
-}
-
-impl fmt::Display for Model {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.write_str(match *self {
- Model::Pro => "Pro",
- Model::Storage => "Storage",
- })
- }
-}
-
-/// The access mode of a volume on the Nitrokey Storage.
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub enum VolumeMode {
- /// A read-only volume.
- ReadOnly,
- /// A read-write volume.
- ReadWrite,
-}
-
-impl fmt::Display for VolumeMode {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.write_str(match *self {
- VolumeMode::ReadOnly => "read-only",
- VolumeMode::ReadWrite => "read-write",
- })
- }
-}
-
-/// A wrapper for a Nitrokey device of unknown type.
-///
-/// Use the [`connect`][] method to obtain a wrapped instance. The wrapper implements all traits
-/// that are shared between all Nitrokey devices so that the shared functionality can be used
-/// without knowing the type of the underlying device. If you want to use functionality that is
-/// not available for all devices, you have to extract the device.
-///
-/// # Examples
-///
-/// Authentication with error handling:
-///
-/// ```no_run
-/// use nitrokey::{Authenticate, DeviceWrapper, User};
-/// # use nitrokey::Error;
-///
-/// fn perform_user_task<'a>(device: &User<'a, DeviceWrapper<'a>>) {}
-/// fn perform_other_task(device: &DeviceWrapper) {}
-///
-/// # fn try_main() -> Result<(), Error> {
-/// let mut manager = nitrokey::take()?;
-/// let device = manager.connect()?;
-/// let device = match device.authenticate_user("123456") {
-/// Ok(user) => {
-/// perform_user_task(&user);
-/// user.device()
-/// },
-/// Err((device, err)) => {
-/// eprintln!("Could not authenticate as user: {}", err);
-/// device
-/// },
-/// };
-/// perform_other_task(&device);
-/// # Ok(())
-/// # }
-/// ```
-///
-/// Device-specific commands:
-///
-/// ```no_run
-/// use nitrokey::{DeviceWrapper, Storage};
-/// # use nitrokey::Error;
-///
-/// fn perform_common_task(device: &DeviceWrapper) {}
-/// fn perform_storage_task(device: &Storage) {}
-///
-/// # fn try_main() -> Result<(), Error> {
-/// let mut manager = nitrokey::take()?;
-/// let device = manager.connect()?;
-/// perform_common_task(&device);
-/// match device {
-/// DeviceWrapper::Storage(storage) => perform_storage_task(&storage),
-/// _ => (),
-/// };
-/// # Ok(())
-/// # }
-/// ```
-///
-/// [`connect`]: struct.Manager.html#method.connect
-#[derive(Debug)]
-pub enum DeviceWrapper<'a> {
- /// A Nitrokey Storage device.
- Storage(Storage<'a>),
- /// A Nitrokey Pro device.
- Pro(Pro<'a>),
-}
-
-/// A Nitrokey Pro device without user or admin authentication.
-///
-/// Use the [`connect`][] method to obtain an instance wrapper or the [`connect_pro`] method to
-/// directly obtain an instance. If you want to execute a command that requires user or admin
-/// authentication, use [`authenticate_admin`][] or [`authenticate_user`][].
-///
-/// # Examples
-///
-/// Authentication with error handling:
-///
-/// ```no_run
-/// use nitrokey::{Authenticate, User, Pro};
-/// # use nitrokey::Error;
-///
-/// fn perform_user_task<'a>(device: &User<'a, Pro<'a>>) {}
-/// fn perform_other_task(device: &Pro) {}
-///
-/// # fn try_main() -> Result<(), Error> {
-/// let mut manager = nitrokey::take()?;
-/// let device = manager.connect_pro()?;
-/// let device = match device.authenticate_user("123456") {
-/// Ok(user) => {
-/// perform_user_task(&user);
-/// user.device()
-/// },
-/// Err((device, err)) => {
-/// eprintln!("Could not authenticate as user: {}", err);
-/// device
-/// },
-/// };
-/// perform_other_task(&device);
-/// # Ok(())
-/// # }
-/// ```
-///
-/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
-/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user
-/// [`connect`]: struct.Manager.html#method.connect
-/// [`connect_pro`]: struct.Manager.html#method.connect_pro
-#[derive(Debug)]
-pub struct Pro<'a> {
- manager: Option<&'a mut crate::Manager>,
-}
+use crate::util::{get_command_result, get_cstring, get_last_error};
/// A Nitrokey Storage device without user or admin authentication.
///
@@ -205,6 +54,24 @@ pub struct Storage<'a> {
manager: Option<&'a mut crate::Manager>,
}
+/// The access mode of a volume on the Nitrokey Storage.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum VolumeMode {
+ /// A read-only volume.
+ ReadOnly,
+ /// A read-write volume.
+ ReadWrite,
+}
+
+impl fmt::Display for VolumeMode {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str(match *self {
+ VolumeMode::ReadOnly => "read-only",
+ VolumeMode::ReadWrite => "read-write",
+ })
+ }
+}
+
/// The status of a volume on a Nitrokey Storage device.
#[derive(Debug)]
pub struct VolumeStatus {
@@ -231,21 +98,6 @@ pub struct SdCardData {
pub manufacturer: u8,
}
-/// A firmware version for a Nitrokey device.
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub struct FirmwareVersion {
- /// The major firmware version, e. g. 0 in v0.40.
- pub major: u8,
- /// The minor firmware version, e. g. 40 in v0.40.
- pub minor: u8,
-}
-
-impl fmt::Display for FirmwareVersion {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "v{}.{}", self.major, self.minor)
- }
-}
-
/// Production information for a Storage device.
#[derive(Debug)]
pub struct StorageProductionInfo {
@@ -289,503 +141,20 @@ pub struct StorageStatus {
pub stick_initialized: bool,
}
-/// A Nitrokey device.
+/// The progress of a background operation on the Nitrokey.
///
-/// This trait provides the commands that can be executed without authentication and that are
-/// present on all supported Nitrokey devices.
-pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt::Debug {
- /// Returns the [`Manager`][] instance that has been used to connect to this device.
- ///
- /// # Example
- ///
- /// ```
- /// use nitrokey::{Device, DeviceWrapper};
- ///
- /// fn do_something(device: DeviceWrapper) {
- /// // reconnect to any device
- /// let manager = device.into_manager();
- /// let device = manager.connect();
- /// // do something with the device
- /// // ...
- /// }
- ///
- /// # fn main() -> Result<(), nitrokey::Error> {
- /// match nitrokey::take()?.connect() {
- /// Ok(device) => do_something(device),
- /// Err(err) => println!("Could not connect to a Nitrokey: {}", err),
- /// }
- /// # Ok(())
- /// # }
- /// ```
- fn into_manager(self) -> &'a mut crate::Manager;
-
- /// Returns the model of the connected Nitrokey device.
- ///
- /// # Example
- ///
- /// ```no_run
- /// use nitrokey::Device;
- /// # use nitrokey::Error;
- ///
- /// # fn try_main() -> Result<(), Error> {
- /// let mut manager = nitrokey::take()?;
- /// let device = manager.connect()?;
- /// println!("Connected to a Nitrokey {}", device.get_model());
- /// # Ok(())
- /// # }
- fn get_model(&self) -> Model;
-
- /// Returns the serial number of the Nitrokey device. The serial number is the string
- /// representation of a hex number.
- ///
- /// # Example
- ///
- /// ```no_run
- /// use nitrokey::Device;
- /// # use nitrokey::Error;
- ///
- /// # fn try_main() -> Result<(), Error> {
- /// let mut manager = nitrokey::take()?;
- /// let device = manager.connect()?;
- /// match device.get_serial_number() {
- /// Ok(number) => println!("serial no: {}", number),
- /// Err(err) => eprintln!("Could not get serial number: {}", err),
- /// };
- /// # Ok(())
- /// # }
- /// ```
- fn get_serial_number(&self) -> Result<String, Error> {
- result_from_string(unsafe { nitrokey_sys::NK_device_serial_number() })
- }
-
- /// Returns the number of remaining authentication attempts for the user. The total number of
- /// available attempts is three.
- ///
- /// # Example
- ///
- /// ```no_run
- /// use nitrokey::Device;
- /// # use nitrokey::Error;
- ///
- /// # fn try_main() -> Result<(), Error> {
- /// let mut manager = nitrokey::take()?;
- /// let device = manager.connect()?;
- /// let count = device.get_user_retry_count();
- /// match device.get_user_retry_count() {
- /// Ok(count) => println!("{} remaining authentication attempts (user)", count),
- /// Err(err) => eprintln!("Could not get user retry count: {}", err),
- /// }
- /// # Ok(())
- /// # }
- /// ```
- fn get_user_retry_count(&self) -> Result<u8, Error> {
- result_or_error(unsafe { nitrokey_sys::NK_get_user_retry_count() })
- }
-
- /// Returns the number of remaining authentication attempts for the admin. The total number of
- /// available attempts is three.
- ///
- /// # Example
- ///
- /// ```no_run
- /// use nitrokey::Device;
- /// # use nitrokey::Error;
- ///
- /// # fn try_main() -> Result<(), Error> {
- /// let mut manager = nitrokey::take()?;
- /// let device = manager.connect()?;
- /// let count = device.get_admin_retry_count();
- /// match device.get_admin_retry_count() {
- /// Ok(count) => println!("{} remaining authentication attempts (admin)", count),
- /// Err(err) => eprintln!("Could not get admin retry count: {}", err),
- /// }
- /// # Ok(())
- /// # }
- /// ```
- fn get_admin_retry_count(&self) -> Result<u8, Error> {
- result_or_error(unsafe { nitrokey_sys::NK_get_admin_retry_count() })
- }
-
- /// Returns the firmware version.
- ///
- /// # Example
- ///
- /// ```no_run
- /// use nitrokey::Device;
- /// # use nitrokey::Error;
- ///
- /// # fn try_main() -> Result<(), Error> {
- /// let mut manager = nitrokey::take()?;
- /// let device = manager.connect()?;
- /// match device.get_firmware_version() {
- /// Ok(version) => println!("Firmware version: {}", version),
- /// Err(err) => eprintln!("Could not access firmware version: {}", err),
- /// };
- /// # Ok(())
- /// # }
- /// ```
- fn get_firmware_version(&self) -> Result<FirmwareVersion, Error> {
- let major = result_or_error(unsafe { nitrokey_sys::NK_get_major_firmware_version() })?;
- let minor = result_or_error(unsafe { nitrokey_sys::NK_get_minor_firmware_version() })?;
- Ok(FirmwareVersion { major, minor })
- }
-
- /// Returns the current configuration of the Nitrokey device.
- ///
- /// # Example
- ///
- /// ```no_run
- /// use nitrokey::Device;
- /// # use nitrokey::Error;
- ///
- /// # fn try_main() -> Result<(), Error> {
- /// let mut manager = nitrokey::take()?;
- /// let device = manager.connect()?;
- /// let config = device.get_config()?;
- /// println!("numlock binding: {:?}", config.numlock);
- /// println!("capslock binding: {:?}", config.capslock);
- /// println!("scrollock binding: {:?}", config.scrollock);
- /// println!("require password for OTP: {:?}", config.user_password);
- /// # Ok(())
- /// # }
- /// ```
- 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())
- }
-
- /// Changes the administrator PIN.
- ///
- /// # Errors
- ///
- /// - [`InvalidString`][] if one of the provided passwords contains a null byte
- /// - [`WrongPassword`][] if the current admin password is wrong
- ///
- /// # Example
- ///
- /// ```no_run
- /// use nitrokey::Device;
- /// # use nitrokey::Error;
- ///
- /// # fn try_main() -> Result<(), Error> {
- /// let mut manager = nitrokey::take()?;
- /// let mut device = manager.connect()?;
- /// match device.change_admin_pin("12345678", "12345679") {
- /// Ok(()) => println!("Updated admin PIN."),
- /// Err(err) => eprintln!("Failed to update admin PIN: {}", err),
- /// };
- /// # Ok(())
- /// # }
- /// ```
- ///
- /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
- /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
- fn change_admin_pin(&mut self, current: &str, new: &str) -> Result<(), Error> {
- let current_string = get_cstring(current)?;
- let new_string = get_cstring(new)?;
- get_command_result(unsafe {
- nitrokey_sys::NK_change_admin_PIN(current_string.as_ptr(), new_string.as_ptr())
- })
- }
-
- /// Changes the user PIN.
- ///
- /// # Errors
- ///
- /// - [`InvalidString`][] if one of the provided passwords contains a null byte
- /// - [`WrongPassword`][] if the current user password is wrong
- ///
- /// # Example
- ///
- /// ```no_run
- /// use nitrokey::Device;
- /// # use nitrokey::Error;
- ///
- /// # fn try_main() -> Result<(), Error> {
- /// let mut manager = nitrokey::take()?;
- /// let mut device = manager.connect()?;
- /// match device.change_user_pin("123456", "123457") {
- /// Ok(()) => println!("Updated admin PIN."),
- /// Err(err) => eprintln!("Failed to update admin PIN: {}", err),
- /// };
- /// # Ok(())
- /// # }
- /// ```
- ///
- /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
- /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
- fn change_user_pin(&mut self, current: &str, new: &str) -> Result<(), Error> {
- let current_string = get_cstring(current)?;
- let new_string = get_cstring(new)?;
- get_command_result(unsafe {
- nitrokey_sys::NK_change_user_PIN(current_string.as_ptr(), new_string.as_ptr())
- })
- }
-
- /// Unlocks the user PIN after three failed login attempts and sets it to the given value.
- ///
- /// # Errors
- ///
- /// - [`InvalidString`][] if one of the provided passwords contains a null byte
- /// - [`WrongPassword`][] if the admin password is wrong
- ///
- /// # Example
- ///
- /// ```no_run
- /// use nitrokey::Device;
- /// # use nitrokey::Error;
- ///
- /// # fn try_main() -> Result<(), Error> {
- /// let mut manager = nitrokey::take()?;
- /// let mut device = manager.connect()?;
- /// match device.unlock_user_pin("12345678", "123456") {
- /// Ok(()) => println!("Unlocked user PIN."),
- /// Err(err) => eprintln!("Failed to unlock user PIN: {}", err),
- /// };
- /// # Ok(())
- /// # }
- /// ```
- ///
- /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
- /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
- fn unlock_user_pin(&mut self, admin_pin: &str, user_pin: &str) -> Result<(), Error> {
- let admin_pin_string = get_cstring(admin_pin)?;
- let user_pin_string = get_cstring(user_pin)?;
- get_command_result(unsafe {
- nitrokey_sys::NK_unlock_user_password(
- admin_pin_string.as_ptr(),
- user_pin_string.as_ptr(),
- )
- })
- }
-
- /// Locks the Nitrokey device.
- ///
- /// This disables the password store if it has been unlocked. On the Nitrokey Storage, this
- /// also disables the volumes if they have been enabled.
- ///
- /// # Example
- ///
- /// ```no_run
- /// use nitrokey::Device;
- /// # use nitrokey::Error;
- ///
- /// # fn try_main() -> Result<(), Error> {
- /// let mut manager = nitrokey::take()?;
- /// let mut device = manager.connect()?;
- /// match device.lock() {
- /// Ok(()) => println!("Locked the Nitrokey device."),
- /// Err(err) => eprintln!("Could not lock the Nitrokey device: {}", err),
- /// };
- /// # Ok(())
- /// # }
- /// ```
- fn lock(&mut self) -> Result<(), Error> {
- get_command_result(unsafe { nitrokey_sys::NK_lock_device() })
- }
-
- /// Performs a factory reset on the Nitrokey device.
- ///
- /// This commands performs a factory reset on the smart card (like the factory reset via `gpg
- /// --card-edit`) and then clears the flash memory (password safe, one-time passwords etc.).
- /// After a factory reset, [`build_aes_key`][] has to be called before the password safe or the
- /// encrypted volume can be used.
- ///
- /// # Errors
- ///
- /// - [`InvalidString`][] if the provided password contains a null byte
- /// - [`WrongPassword`][] if the admin password is wrong
- ///
- /// # Example
- ///
- /// ```no_run
- /// use nitrokey::Device;
- /// # use nitrokey::Error;
- ///
- /// # fn try_main() -> Result<(), Error> {
- /// let mut manager = nitrokey::take()?;
- /// let mut device = manager.connect()?;
- /// match device.factory_reset("12345678") {
- /// Ok(()) => println!("Performed a factory reset."),
- /// Err(err) => eprintln!("Could not perform a factory reset: {}", err),
- /// };
- /// # Ok(())
- /// # }
- /// ```
- ///
- /// [`build_aes_key`]: #method.build_aes_key
- fn factory_reset(&mut self, admin_pin: &str) -> Result<(), Error> {
- let admin_pin_string = get_cstring(admin_pin)?;
- get_command_result(unsafe { nitrokey_sys::NK_factory_reset(admin_pin_string.as_ptr()) })
- }
-
- /// Builds a new AES key on the Nitrokey.
- ///
- /// The AES key is used to encrypt the password safe and the encrypted volume. You may need
- /// to call this method after a factory reset, either using [`factory_reset`][] or using `gpg
- /// --card-edit`. You can also use it to destroy the data stored in the password safe or on
- /// the encrypted volume.
- ///
- /// # Errors
- ///
- /// - [`InvalidString`][] if the provided password contains a null byte
- /// - [`WrongPassword`][] if the admin password is wrong
- ///
- /// # Example
- ///
- /// ```no_run
- /// use nitrokey::Device;
- /// # use nitrokey::Error;
- ///
- /// # fn try_main() -> Result<(), Error> {
- /// let mut manager = nitrokey::take()?;
- /// let mut device = manager.connect()?;
- /// match device.build_aes_key("12345678") {
- /// Ok(()) => println!("New AES keys have been built."),
- /// Err(err) => eprintln!("Could not build new AES keys: {}", err),
- /// };
- /// # Ok(())
- /// # }
- /// ```
- ///
- /// [`factory_reset`]: #method.factory_reset
- fn build_aes_key(&mut self, admin_pin: &str) -> Result<(), Error> {
- let admin_pin_string = get_cstring(admin_pin)?;
- get_command_result(unsafe { nitrokey_sys::NK_build_aes_key(admin_pin_string.as_ptr()) })
- }
-}
-
-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,
- }
-}
-
-pub(crate) fn create_device_wrapper(
- manager: &mut crate::Manager,
- model: Model,
-) -> DeviceWrapper<'_> {
- match model {
- Model::Pro => Pro::new(manager).into(),
- Model::Storage => Storage::new(manager).into(),
- }
-}
-
-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()),
- }
-}
-
-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 }
-}
-
-impl<'a> DeviceWrapper<'a> {
- fn device(&self) -> &dyn Device<'a> {
- match *self {
- DeviceWrapper::Storage(ref storage) => storage,
- DeviceWrapper::Pro(ref pro) => pro,
- }
- }
-
- fn device_mut(&mut self) -> &mut dyn Device<'a> {
- match *self {
- DeviceWrapper::Storage(ref mut storage) => storage,
- DeviceWrapper::Pro(ref mut pro) => pro,
- }
- }
-}
-
-impl<'a> From<Pro<'a>> for DeviceWrapper<'a> {
- fn from(device: Pro<'a>) -> Self {
- DeviceWrapper::Pro(device)
- }
-}
-
-impl<'a> From<Storage<'a>> for DeviceWrapper<'a> {
- fn from(device: Storage<'a>) -> Self {
- DeviceWrapper::Storage(device)
- }
-}
-
-impl<'a> GenerateOtp for DeviceWrapper<'a> {
- fn get_hotp_slot_name(&self, slot: u8) -> Result<String, Error> {
- self.device().get_hotp_slot_name(slot)
- }
-
- fn get_totp_slot_name(&self, slot: u8) -> Result<String, Error> {
- self.device().get_totp_slot_name(slot)
- }
-
- fn get_hotp_code(&mut self, slot: u8) -> Result<String, Error> {
- self.device_mut().get_hotp_code(slot)
- }
-
- fn get_totp_code(&self, slot: u8) -> Result<String, Error> {
- self.device().get_totp_code(slot)
- }
-}
-
-impl<'a> Device<'a> for DeviceWrapper<'a> {
- fn into_manager(self) -> &'a mut crate::Manager {
- match self {
- DeviceWrapper::Pro(dev) => dev.into_manager(),
- DeviceWrapper::Storage(dev) => dev.into_manager(),
- }
- }
-
- fn get_model(&self) -> Model {
- match *self {
- DeviceWrapper::Pro(_) => Model::Pro,
- DeviceWrapper::Storage(_) => Model::Storage,
- }
- }
-}
-
-impl<'a> Pro<'a> {
- pub(crate) fn new(manager: &'a mut crate::Manager) -> Pro<'a> {
- Pro {
- manager: Some(manager),
- }
- }
-}
-
-impl<'a> Drop for Pro<'a> {
- fn drop(&mut self) {
- unsafe {
- nitrokey_sys::NK_logout();
- }
- }
-}
-
-impl<'a> Device<'a> for Pro<'a> {
- fn into_manager(mut self) -> &'a mut crate::Manager {
- self.manager.take().unwrap()
- }
-
- fn get_model(&self) -> Model {
- Model::Pro
- }
+/// 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> GenerateOtp for Pro<'a> {}
-
impl<'a> Storage<'a> {
pub(crate) fn new(manager: &'a mut crate::Manager) -> Storage<'a> {
Storage {
@@ -1170,7 +539,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);
/// },
@@ -1179,7 +548,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,
@@ -1280,11 +649,121 @@ 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("Invalid write levels".to_owned()))
+ } 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("Cannot create u8 from operation status".to_owned())
+ }),
+ -1 => Ok(OperationStatus::Idle),
+ -2 => Err(get_last_error()),
+ _ => Err(Error::UnexpectedError(
+ "Invalid operation status".to_owned(),
+ )),
+ }
+ }
+
+ /// 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
@@ -1323,6 +802,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 = SerialNumber::new(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
new file mode 100644
index 0000000..942a905
--- /dev/null
+++ b/src/device/wrapper.rs
@@ -0,0 +1,142 @@
+// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
+// SPDX-License-Identifier: MIT
+
+use crate::device::{Device, Model, Pro, Status, Storage};
+use crate::error::Error;
+use crate::otp::GenerateOtp;
+
+/// A wrapper for a Nitrokey device of unknown type.
+///
+/// Use the [`connect`][] method to obtain a wrapped instance. The wrapper implements all traits
+/// that are shared between all Nitrokey devices so that the shared functionality can be used
+/// without knowing the type of the underlying device. If you want to use functionality that is
+/// not available for all devices, you have to extract the device.
+///
+/// # Examples
+///
+/// Authentication with error handling:
+///
+/// ```no_run
+/// use nitrokey::{Authenticate, DeviceWrapper, User};
+/// # use nitrokey::Error;
+///
+/// fn perform_user_task<'a>(device: &User<'a, DeviceWrapper<'a>>) {}
+/// fn perform_other_task(device: &DeviceWrapper) {}
+///
+/// # fn try_main() -> Result<(), Error> {
+/// let mut manager = nitrokey::take()?;
+/// let device = manager.connect()?;
+/// let device = match device.authenticate_user("123456") {
+/// Ok(user) => {
+/// perform_user_task(&user);
+/// user.device()
+/// },
+/// Err((device, err)) => {
+/// eprintln!("Could not authenticate as user: {}", err);
+/// device
+/// },
+/// };
+/// perform_other_task(&device);
+/// # Ok(())
+/// # }
+/// ```
+///
+/// Device-specific commands:
+///
+/// ```no_run
+/// use nitrokey::{DeviceWrapper, Storage};
+/// # use nitrokey::Error;
+///
+/// fn perform_common_task(device: &DeviceWrapper) {}
+/// fn perform_storage_task(device: &Storage) {}
+///
+/// # fn try_main() -> Result<(), Error> {
+/// let mut manager = nitrokey::take()?;
+/// let device = manager.connect()?;
+/// perform_common_task(&device);
+/// match device {
+/// DeviceWrapper::Storage(storage) => perform_storage_task(&storage),
+/// _ => (),
+/// };
+/// # Ok(())
+/// # }
+/// ```
+///
+/// [`connect`]: struct.Manager.html#method.connect
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum DeviceWrapper<'a> {
+ /// A Nitrokey Storage device.
+ Storage(Storage<'a>),
+ /// A Nitrokey Pro device.
+ Pro(Pro<'a>),
+}
+
+impl<'a> DeviceWrapper<'a> {
+ fn device(&self) -> &dyn Device<'a> {
+ match *self {
+ DeviceWrapper::Storage(ref storage) => storage,
+ DeviceWrapper::Pro(ref pro) => pro,
+ }
+ }
+
+ fn device_mut(&mut self) -> &mut dyn Device<'a> {
+ match *self {
+ DeviceWrapper::Storage(ref mut storage) => storage,
+ DeviceWrapper::Pro(ref mut pro) => pro,
+ }
+ }
+}
+
+impl<'a> From<Pro<'a>> for DeviceWrapper<'a> {
+ fn from(device: Pro<'a>) -> Self {
+ DeviceWrapper::Pro(device)
+ }
+}
+
+impl<'a> From<Storage<'a>> for DeviceWrapper<'a> {
+ fn from(device: Storage<'a>) -> Self {
+ DeviceWrapper::Storage(device)
+ }
+}
+
+impl<'a> GenerateOtp for DeviceWrapper<'a> {
+ fn get_hotp_slot_name(&self, slot: u8) -> Result<String, Error> {
+ self.device().get_hotp_slot_name(slot)
+ }
+
+ fn get_totp_slot_name(&self, slot: u8) -> Result<String, Error> {
+ self.device().get_totp_slot_name(slot)
+ }
+
+ fn get_hotp_code(&mut self, slot: u8) -> Result<String, Error> {
+ self.device_mut().get_hotp_code(slot)
+ }
+
+ fn get_totp_code(&self, slot: u8) -> Result<String, Error> {
+ self.device().get_totp_code(slot)
+ }
+}
+
+impl<'a> Device<'a> for DeviceWrapper<'a> {
+ fn into_manager(self) -> &'a mut crate::Manager {
+ match self {
+ DeviceWrapper::Pro(dev) => dev.into_manager(),
+ DeviceWrapper::Storage(dev) => dev.into_manager(),
+ }
+ }
+
+ fn get_model(&self) -> Model {
+ match *self {
+ DeviceWrapper::Pro(_) => Model::Pro,
+ 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(),
+ }
+ }
+}
diff --git a/src/error.rs b/src/error.rs
index 9e6adc0..f1e91c3 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -11,6 +11,7 @@ use crate::device;
/// An error returned by the nitrokey crate.
#[derive(Debug)]
+#[non_exhaustive]
pub enum Error {
/// An error reported by the Nitrokey device in the response packet.
CommandError(CommandError),
@@ -21,13 +22,13 @@ pub enum Error {
/// A library usage error.
LibraryError(LibraryError),
/// An error that occurred due to a poisoned lock.
- PoisonError(sync::PoisonError<sync::MutexGuard<'static, crate::Manager>>),
- /// An error that occurred during random number generation.
- RandError(Box<dyn error::Error>),
+ PoisonError,
/// An error that is caused by an unexpected value returned by libnitrokey.
- UnexpectedError,
+ UnexpectedError(String),
/// An unknown error returned by libnitrokey.
UnknownError(i64),
+ /// An error caused by a Nitrokey model that is not supported by this crate.
+ UnsupportedModelError,
/// An error occurred when interpreting a UTF-8 string.
Utf8Error(str::Utf8Error),
}
@@ -70,14 +71,14 @@ impl From<str::Utf8Error> for Error {
}
}
-impl From<sync::PoisonError<sync::MutexGuard<'static, crate::Manager>>> for Error {
- fn from(error: sync::PoisonError<sync::MutexGuard<'static, crate::Manager>>) -> Self {
- Error::PoisonError(error)
+impl<T> From<sync::PoisonError<T>> for Error {
+ fn from(_error: sync::PoisonError<T>) -> Self {
+ Error::PoisonError
}
}
-impl From<sync::TryLockError<sync::MutexGuard<'static, crate::Manager>>> for Error {
- fn from(error: sync::TryLockError<sync::MutexGuard<'static, crate::Manager>>) -> Self {
+impl<T> From<sync::TryLockError<T>> for Error {
+ fn from(error: sync::TryLockError<T>) -> Self {
match error {
sync::TryLockError::Poisoned(err) => err.into(),
sync::TryLockError::WouldBlock => Error::ConcurrentAccessError,
@@ -91,21 +92,7 @@ impl<'a, T: device::Device<'a>> From<(T, Error)> for Error {
}
}
-impl error::Error for Error {
- fn source(&self) -> Option<&(dyn error::Error + 'static)> {
- match *self {
- Error::CommandError(ref err) => Some(err),
- Error::CommunicationError(ref err) => Some(err),
- Error::ConcurrentAccessError => None,
- Error::LibraryError(ref err) => Some(err),
- Error::PoisonError(ref err) => Some(err),
- Error::RandError(ref err) => Some(err.as_ref()),
- Error::UnexpectedError => None,
- Error::UnknownError(_) => None,
- Error::Utf8Error(ref err) => Some(err),
- }
- }
-}
+impl error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -114,10 +101,10 @@ impl fmt::Display for Error {
Error::CommunicationError(ref err) => write!(f, "Communication error: {}", err),
Error::ConcurrentAccessError => write!(f, "Internal error: concurrent access"),
Error::LibraryError(ref err) => write!(f, "Library error: {}", err),
- Error::PoisonError(_) => write!(f, "Internal error: poisoned lock"),
- Error::RandError(ref err) => write!(f, "RNG error: {}", err),
- Error::UnexpectedError => write!(f, "An unexpected error occurred"),
+ Error::PoisonError => write!(f, "Internal error: poisoned lock"),
+ Error::UnexpectedError(ref s) => write!(f, "An unexpected error occurred: {}", s),
Error::UnknownError(ref err) => write!(f, "Unknown error: {}", err),
+ Error::UnsupportedModelError => write!(f, "Unsupported Nitrokey model"),
Error::Utf8Error(ref err) => write!(f, "UTF-8 error: {}", err),
}
}
@@ -125,6 +112,7 @@ impl fmt::Display for Error {
/// An error reported by the Nitrokey device in the response packet.
#[derive(Clone, Copy, Debug, PartialEq)]
+#[non_exhaustive]
pub enum CommandError {
/// A packet with a wrong checksum has been sent or received.
WrongCrc,
@@ -191,6 +179,7 @@ impl fmt::Display for CommandError {
/// A device communication error.
#[derive(Clone, Copy, Debug, PartialEq)]
+#[non_exhaustive]
pub enum CommunicationError {
/// Could not connect to a Nitrokey device.
NotConnected,
@@ -229,6 +218,7 @@ impl fmt::Display for CommunicationError {
/// A library usage error.
#[derive(Clone, Copy, Debug, PartialEq)]
+#[non_exhaustive]
pub enum LibraryError {
/// A supplied string exceeded a length limit.
StringTooLong,
@@ -267,3 +257,22 @@ impl fmt::Display for LibraryError {
})
}
}
+
+// build our own static assertion that Error implements error::Error, Send, Sync, 'static
+
+struct Helper<T>(T);
+
+trait Assert {
+ fn assert() -> bool;
+}
+
+impl<T: error::Error + Send + Sync + 'static> Assert for Helper<T> {
+ fn assert() -> bool {
+ true
+ }
+}
+
+#[allow(unused)]
+fn assert_error_impl() {
+ let _ = Helper::<Error>::assert();
+}
diff --git a/src/lib.rs b/src/lib.rs
index a4402c5..1f1ae27 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,6 +16,10 @@
//! [`connect_model`][], [`connect_pro`][] or [`connect_storage`][] to connect to a specific
//! device.
//!
+//! To get a list of all connected Nitrokey devices, use the [`list_devices`][] function. You can
+//! then connect to one of the connected devices using the [`connect_path`][] function of the
+//! `Manager` struct.
+//!
//! You can call [`authenticate_user`][] or [`authenticate_admin`][] to get an authenticated device
//! that can perform operations that require authentication. You can use [`device`][] to go back
//! to the unauthenticated device.
@@ -25,6 +29,15 @@
//! passwords – [`get_hotp_code`][] and [`get_totp_code`][]. Depending on the stick configuration,
//! these operations are available without authentication or with user authentication.
//!
+//! # Background operations
+//!
+//! Some commands may start background operations. During such an operation, every new command
+//! will cause a [`WrongCrc`][] error. To check whether a background operation is currently
+//! running, use the [`get_operation_status`][] method.
+//!
+//! Background operations are only available on the Nitrokey Storage. Currently,
+//! [`fill_sd_card`][] is the only command that triggers a background operation.
+//!
//! # Examples
//!
//! Connect to any Nitrokey and print its serial number:
@@ -86,8 +99,12 @@
//! [`take`]: fn.take.html
//! [`connect`]: struct.Manager.html#method.connect
//! [`connect_model`]: struct.Manager.html#method.connect_model
+//! [`connect_path`]: struct.Manager.html#method.connect_path
//! [`connect_pro`]: struct.Manager.html#method.connect_pro
//! [`connect_storage`]: struct.Manager.html#method.connect_storage
+//! [`fill_sd_card`]: struct.Storage.html#method.fill_sd_card
+//! [`get_operation_status`]: struct.Storage.html#method.get_operation_status
+//! [`list_devices`]: fn.list_devices.html
//! [`manager`]: trait.Device.html#method.manager
//! [`device`]: struct.User.html#method.device
//! [`get_hotp_code`]: trait.GenerateOtp.html#method.get_hotp_code
@@ -95,6 +112,7 @@
//! [`Admin`]: struct.Admin.html
//! [`DeviceWrapper`]: enum.DeviceWrapper.html
//! [`User`]: struct.User.html
+//! [`WrongCrc`]: enum.CommandError.html#variant.WrongCrc
#![warn(missing_docs, rust_2018_compatibility, rust_2018_idioms, unused)]
@@ -109,23 +127,25 @@ mod otp;
mod pws;
mod util;
+use std::convert::TryInto as _;
use std::fmt;
use std::marker;
+use std::ptr::NonNull;
use std::sync;
-use nitrokey_sys;
-
pub use crate::auth::{Admin, Authenticate, User};
pub use crate::config::Config;
pub use crate::device::{
- Device, DeviceWrapper, Model, Pro, SdCardData, Storage, StorageProductionInfo, StorageStatus,
- VolumeMode, VolumeStatus,
+ Device, DeviceInfo, DeviceWrapper, FirmwareVersion, Model, OperationStatus, Pro, SdCardData,
+ SerialNumber, Status, Storage, StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus,
};
pub use crate::error::{CommandError, CommunicationError, Error, LibraryError};
pub use crate::otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData};
pub use crate::pws::{GetPasswordSafe, PasswordSafe, SLOT_COUNT};
pub use crate::util::LogLevel;
+use crate::util::{get_cstring, get_last_result};
+
/// The default admin PIN for all Nitrokey devices.
pub const DEFAULT_ADMIN_PIN: &str = "12345678";
/// The default user PIN for all Nitrokey devices.
@@ -235,6 +255,7 @@ impl Manager {
/// # Errors
///
/// - [`NotConnected`][] if no Nitrokey device is connected
+ /// - [`UnsupportedModelError`][] if the Nitrokey device is not supported by this crate
///
/// # Example
///
@@ -243,17 +264,16 @@ impl Manager {
///
/// fn do_something(device: DeviceWrapper) {}
///
- /// # fn main() -> Result<(), nitrokey::Error> {
/// let mut manager = nitrokey::take()?;
/// match manager.connect() {
/// Ok(device) => do_something(device),
/// Err(err) => println!("Could not connect to a Nitrokey: {}", err),
/// }
- /// # Ok(())
- /// # }
+ /// # Ok::<(), nitrokey::Error>(())
/// ```
///
/// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
+ /// [`UnsupportedModelError`]: enum.Error.html#variant.UnsupportedModelError
pub fn connect(&mut self) -> Result<DeviceWrapper<'_>, Error> {
if unsafe { nitrokey_sys::NK_login_auto() } == 1 {
device::get_connected_device(self)
@@ -276,13 +296,11 @@ impl Manager {
///
/// fn do_something(device: DeviceWrapper) {}
///
- /// # fn main() -> Result<(), nitrokey::Error> {
/// match nitrokey::take()?.connect_model(Model::Pro) {
/// Ok(device) => do_something(device),
/// Err(err) => println!("Could not connect to a Nitrokey Pro: {}", err),
/// }
- /// # Ok(())
- /// # }
+ /// # Ok::<(), nitrokey::Error>(())
/// ```
///
/// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
@@ -294,6 +312,49 @@ impl Manager {
}
}
+ /// Connects to a Nitrokey device at the given USB path.
+ ///
+ /// To get a list of all connected Nitrokey devices, use the [`list_devices`][] function. The
+ /// [`DeviceInfo`][] structs returned by that function contain the USB path in the `path`
+ /// field.
+ ///
+ /// # Errors
+ ///
+ /// - [`InvalidString`][] if the USB path contains a null byte
+ /// - [`NotConnected`][] if no Nitrokey device can be found at the given USB path
+ /// - [`UnsupportedModelError`][] if the model of the Nitrokey device at the given USB path is
+ /// not supported by this crate
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use nitrokey::DeviceWrapper;
+ ///
+ /// fn use_device(device: DeviceWrapper) {}
+ ///
+ /// let mut manager = nitrokey::take()?;
+ /// let devices = nitrokey::list_devices()?;
+ /// for device in devices {
+ /// let device = manager.connect_path(device.path)?;
+ /// use_device(device);
+ /// }
+ /// # Ok::<(), nitrokey::Error>(())
+ /// ```
+ ///
+ /// [`list_devices`]: fn.list_devices.html
+ /// [`DeviceInfo`]: struct.DeviceInfo.html
+ /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
+ /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
+ /// [`UnsupportedModelError`]: enum.Error.html#variant.UnsupportedModelError
+ pub fn connect_path<S: Into<Vec<u8>>>(&mut self, path: S) -> Result<DeviceWrapper<'_>, Error> {
+ let path = get_cstring(path)?;
+ if unsafe { nitrokey_sys::NK_connect_with_path(path.as_ptr()) } == 1 {
+ device::get_connected_device(self)
+ } else {
+ Err(CommunicationError::NotConnected.into())
+ }
+ }
+
/// Connects to a Nitrokey Pro.
///
/// # Errors
@@ -307,13 +368,11 @@ impl Manager {
///
/// fn use_pro(device: Pro) {}
///
- /// # fn main() -> Result<(), nitrokey::Error> {
/// match nitrokey::take()?.connect_pro() {
/// Ok(device) => use_pro(device),
/// Err(err) => println!("Could not connect to the Nitrokey Pro: {}", err),
/// }
- /// # Ok(())
- /// # }
+ /// # Ok::<(), nitrokey::Error>(())
/// ```
///
/// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
@@ -338,13 +397,11 @@ impl Manager {
///
/// fn use_storage(device: Storage) {}
///
- /// # fn main() -> Result<(), nitrokey::Error> {
/// match nitrokey::take()?.connect_storage() {
/// Ok(device) => use_storage(device),
/// Err(err) => println!("Could not connect to the Nitrokey Storage: {}", err),
/// }
- /// # Ok(())
- /// # }
+ /// # Ok::<(), nitrokey::Error>(())
/// ```
///
/// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
@@ -413,15 +470,77 @@ pub fn take() -> Result<sync::MutexGuard<'static, Manager>, Error> {
/// [`ConcurrentAccessError`]: struct.Error.html#variant.ConcurrentAccessError
/// [`Manager`]: struct.Manager.html
pub fn force_take() -> Result<sync::MutexGuard<'static, Manager>, Error> {
- match take() {
- Ok(guard) => Ok(guard),
- Err(err) => match err {
- Error::PoisonError(err) => Ok(err.into_inner()),
- err => Err(err),
- },
+ MANAGER.try_lock().or_else(|err| match err {
+ sync::TryLockError::Poisoned(err) => Ok(err.into_inner()),
+ sync::TryLockError::WouldBlock => Err(Error::ConcurrentAccessError),
+ })
+}
+
+/// List all connected Nitrokey devices.
+///
+/// This functions returns a vector with [`DeviceInfo`][] structs that contain information about
+/// all connected Nitrokey devices. It will even list unsupported models, although you cannot
+/// connect to them. To connect to a supported model, call the [`connect_path`][] function.
+///
+/// # Errors
+///
+/// - [`NotConnected`][] if a Nitrokey device has been disconnected during enumeration
+/// - [`Utf8Error`][] if the USB path or the serial number returned by libnitrokey are invalid
+/// UTF-8 strings
+///
+/// # Example
+///
+/// ```
+/// let devices = nitrokey::list_devices()?;
+/// if devices.is_empty() {
+/// println!("No connected Nitrokey devices found.");
+/// } else {
+/// println!("model\tpath\tserial number");
+/// for device in devices {
+/// match device.model {
+/// Some(model) => print!("{}", model),
+/// None => print!("unsupported"),
+/// }
+/// print!("\t{}\t", device.path);
+/// match device.serial_number {
+/// Some(serial_number) => println!("{}", serial_number),
+/// None => println!("unknown"),
+/// }
+/// }
+/// }
+/// # Ok::<(), nitrokey::Error>(())
+/// ```
+///
+/// [`connect_path`]: struct.Manager.html#fn.connect_path
+/// [`DeviceInfo`]: struct.DeviceInfo.html
+/// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
+/// [`Utf8Error`]: enum.Error.html#variant.Utf8Error
+pub fn list_devices() -> Result<Vec<DeviceInfo>, Error> {
+ let ptr = NonNull::new(unsafe { nitrokey_sys::NK_list_devices() });
+ match ptr {
+ Some(mut ptr) => {
+ let mut vec: Vec<DeviceInfo> = Vec::new();
+ push_device_info(&mut vec, unsafe { ptr.as_ref() })?;
+ unsafe {
+ nitrokey_sys::NK_free_device_info(ptr.as_mut());
+ }
+ Ok(vec)
+ }
+ None => get_last_result().map(|_| Vec::new()),
}
}
+fn push_device_info(
+ vec: &mut Vec<DeviceInfo>,
+ info: &nitrokey_sys::NK_device_info,
+) -> Result<(), Error> {
+ vec.push(info.try_into()?);
+ if let Some(ptr) = NonNull::new(info.next) {
+ push_device_info(vec, unsafe { ptr.as_ref() })?;
+ }
+ Ok(())
+}
+
/// Enables or disables debug output. Calling this method with `true` is equivalent to setting the
/// log level to `Debug`; calling it with `false` is equivalent to the log level `Error` (see
/// [`set_log_level`][]).
@@ -453,11 +572,9 @@ pub fn set_log_level(level: LogLevel) {
/// # Example
///
/// ```
-/// # fn main() -> Result<(), nitrokey::Error> {
/// let version = nitrokey::get_library_version()?;
/// println!("Using libnitrokey {}", version.git);
-/// # Ok(())
-/// # }
+/// # Ok::<(), nitrokey::Error>(())
/// ```
///
/// [`Utf8Error`]: enum.Error.html#variant.Utf8Error
diff --git a/src/otp.rs b/src/otp.rs
index 4667aff..7b47a3b 100644
--- a/src/otp.rs
+++ b/src/otp.rs
@@ -3,8 +3,6 @@
use std::ffi::CString;
-use nitrokey_sys;
-
use crate::error::Error;
use crate::util::{get_command_result, get_cstring, result_from_string};
@@ -349,8 +347,8 @@ pub struct OtpSlotData {
pub secret: String,
/// The OTP generation mode.
pub mode: OtpMode,
- /// If true, press the enter key after sending an OTP code using double-pressed
- /// numlock, capslock or scrolllock.
+ /// If true, press the enter key after sending an OTP code using double-pressed Num Lock, Caps
+ /// Lock or Scroll Lock.
pub use_enter: bool,
/// Set the token ID, see [OATH Token Identifier Specification][tokspec], section “Class A”.
///
@@ -387,8 +385,8 @@ impl OtpSlotData {
}
}
- /// Enables pressing the enter key after sending an OTP code using double-pressed numlock,
- /// capslock or scrollock.
+ /// Enables pressing the enter key after sending an OTP code using double-pressed Num Lock,
+ /// Caps Lock or Scroll Lock.
pub fn use_enter(mut self) -> OtpSlotData {
self.use_enter = true;
self
diff --git a/src/pws.rs b/src/pws.rs
index 3398deb..93294c5 100644
--- a/src/pws.rs
+++ b/src/pws.rs
@@ -1,9 +1,6 @@
// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT
-use libc;
-use nitrokey_sys;
-
use crate::device::{Device, DeviceWrapper, Pro, Storage};
use crate::error::{CommandError, Error};
use crate::util::{get_command_result, get_cstring, get_last_error, result_from_string};
diff --git a/src/util.rs b/src/util.rs
index a5dd1e5..26942cf 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -5,8 +5,7 @@ use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int};
use libc::{c_void, free};
-use rand_core::RngCore;
-use rand_os::OsRng;
+use rand_core::{OsRng, RngCore};
use crate::error::{Error, LibraryError};
@@ -31,26 +30,38 @@ pub enum LogLevel {
DebugL2,
}
+pub fn str_from_ptr<'a>(ptr: *const c_char) -> Result<&'a str, Error> {
+ unsafe { CStr::from_ptr(ptr) }.to_str().map_err(Error::from)
+}
+
pub fn owned_str_from_ptr(ptr: *const c_char) -> Result<String, Error> {
- unsafe { CStr::from_ptr(ptr) }
- .to_str()
- .map(String::from)
- .map_err(Error::from)
+ str_from_ptr(ptr).map(ToOwned::to_owned)
}
-pub fn result_from_string(ptr: *const c_char) -> Result<String, Error> {
+pub fn run_with_string<R, F>(ptr: *const c_char, op: F) -> Result<R, Error>
+where
+ F: FnOnce(&str) -> Result<R, Error>,
+{
if ptr.is_null() {
- return Err(Error::UnexpectedError);
+ return Err(Error::UnexpectedError(
+ "libnitrokey returned a null pointer".to_owned(),
+ ));
}
- let s = owned_str_from_ptr(ptr)?;
+ let result = str_from_ptr(ptr).and_then(op);
unsafe { free(ptr as *mut c_void) };
- // An empty string can both indicate an error or be a valid return value. In this case, we
- // have to check the last command status to decide what to return.
- if s.is_empty() {
- get_last_result().map(|_| s)
- } else {
- Ok(s)
- }
+ result
+}
+
+pub fn result_from_string(ptr: *const c_char) -> Result<String, Error> {
+ run_with_string(ptr, |s| {
+ // An empty string can both indicate an error or be a valid return value. In this case, we
+ // have to check the last command status to decide what to return.
+ if s.is_empty() {
+ get_last_result().map(|_| s.to_owned())
+ } else {
+ Ok(s.to_owned())
+ }
+ })
}
pub fn result_or_error<T>(value: T) -> Result<T, Error> {
@@ -70,20 +81,25 @@ pub fn get_last_result() -> Result<(), Error> {
}
pub fn get_last_error() -> Error {
- match get_last_result() {
- Ok(()) => Error::UnexpectedError,
- Err(err) => err,
- }
+ get_last_result().err().unwrap_or_else(|| {
+ Error::UnexpectedError("Expected an error, but command status is zero".to_owned())
+ })
}
-pub fn generate_password(length: usize) -> Result<Vec<u8>, Error> {
- let mut data = vec![0u8; length];
- OsRng.fill_bytes(&mut data[..]);
- Ok(data)
+pub fn generate_password(length: usize) -> Result<CString, Error> {
+ loop {
+ // Randomly generate a password until we get a string *without* null bytes. Otherwise
+ // the string would be cut off prematurely due to null-termination in C.
+ let mut data = vec![0u8; length];
+ OsRng.fill_bytes(&mut data[..]);
+ if let Ok(s) = CString::new(data) {
+ return Ok(s);
+ }
+ }
}
pub fn get_cstring<T: Into<Vec<u8>>>(s: T) -> Result<CString, Error> {
- CString::new(s).or_else(|_| Err(LibraryError::InvalidString.into()))
+ CString::new(s).map_err(|_| LibraryError::InvalidString.into())
}
impl Into<i32> for LogLevel {