summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/device/mod.rs209
-rw-r--r--src/device/pro.rs18
-rw-r--r--src/device/storage.rs161
-rw-r--r--src/device/wrapper.rs9
-rw-r--r--src/error.rs4
-rw-r--r--src/lib.rs136
6 files changed, 510 insertions, 27 deletions
diff --git a/src/device/mod.rs b/src/device/mod.rs
index 5e15f08..16d6b11 100644
--- a/src/device/mod.rs
+++ b/src/device/mod.rs
@@ -5,6 +5,9 @@ mod pro;
mod storage;
mod wrapper;
+use std::cmp;
+use std::convert::{TryFrom, TryInto};
+use std::ffi;
use std::fmt;
use libc;
@@ -16,12 +19,14 @@ use crate::error::{CommunicationError, Error};
use crate::otp::GenerateOtp;
use crate::pws::GetPasswordSafe;
use crate::util::{
- get_command_result, get_cstring, get_last_error, result_from_string, result_or_error,
+ get_command_result, get_cstring, get_last_error, owned_str_from_ptr, result_from_string,
+ result_or_error,
};
pub use pro::Pro;
pub use storage::{
- SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode, VolumeStatus,
+ OperationStatus, SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode,
+ VolumeStatus,
};
pub use wrapper::DeviceWrapper;
@@ -43,6 +48,97 @@ impl fmt::Display for Model {
}
}
+impl From<Model> for nitrokey_sys::NK_device_model {
+ fn from(model: Model) -> Self {
+ match model {
+ Model::Storage => nitrokey_sys::NK_device_model_NK_STORAGE,
+ Model::Pro => nitrokey_sys::NK_device_model_NK_PRO,
+ }
+ }
+}
+
+impl TryFrom<nitrokey_sys::NK_device_model> for Model {
+ type Error = Error;
+
+ fn try_from(model: nitrokey_sys::NK_device_model) -> Result<Self, Error> {
+ match model {
+ nitrokey_sys::NK_device_model_NK_DISCONNECTED => {
+ Err(CommunicationError::NotConnected.into())
+ }
+ nitrokey_sys::NK_device_model_NK_PRO => Ok(Model::Pro),
+ nitrokey_sys::NK_device_model_NK_STORAGE => Ok(Model::Storage),
+ _ => Err(Error::UnsupportedModelError),
+ }
+ }
+}
+
+/// Connection information for a Nitrokey device.
+#[derive(Clone, Debug, PartialEq)]
+pub struct DeviceInfo {
+ /// The model of the Nitrokey device, or `None` if the model is not supported by this crate.
+ pub model: Option<Model>,
+ /// The USB device path.
+ pub path: String,
+ /// The serial number as a 8-character hex string, or `None` if the device does not expose its
+ /// serial number.
+ pub serial_number: Option<String>,
+}
+
+impl TryFrom<&nitrokey_sys::NK_device_info> for DeviceInfo {
+ type Error = Error;
+
+ fn try_from(device_info: &nitrokey_sys::NK_device_info) -> Result<DeviceInfo, Error> {
+ let model_result = device_info.model.try_into();
+ let model_option = model_result.map(Some).or_else(|err| match err {
+ Error::UnsupportedModelError => Ok(None),
+ _ => Err(err),
+ })?;
+ let serial_number = unsafe { ffi::CStr::from_ptr(device_info.serial_number) }
+ .to_str()
+ .map_err(Error::from)?;
+ Ok(DeviceInfo {
+ model: model_option,
+ path: owned_str_from_ptr(device_info.path)?,
+ serial_number: get_hidapi_serial_number(serial_number),
+ })
+ }
+}
+
+impl fmt::Display for DeviceInfo {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self.model {
+ Some(model) => write!(f, "Nitrokey {}", model)?,
+ None => write!(f, "Unsupported Nitrokey model")?,
+ }
+ write!(f, " at {} with ", self.path)?;
+ match &self.serial_number {
+ Some(ref serial_number) => write!(f, "serial no. {}", serial_number),
+ None => write!(f, "an unknown serial number"),
+ }
+ }
+}
+
+/// Parses a serial number returned by hidapi and transforms it to the Nitrokey format.
+///
+/// If the serial number is all zero, this function returns `None`. Otherwise, all leading zeros
+/// (except the last eight characters) are stripped and `Some(_)` is returned. If the serial
+/// number has less than eight characters, leading zeros are added.
+fn get_hidapi_serial_number(serial_number: &str) -> Option<String> {
+ let mut iter = serial_number.char_indices().skip_while(|(_, c)| *c == '0');
+ let first_non_null = iter.next();
+ if let Some((i, _)) = first_non_null {
+ // keep at least the last 8 characters
+ let len = serial_number.len();
+ let cut = cmp::min(len.saturating_sub(8), i);
+ let (_, suffix) = serial_number.split_at(cut);
+ // if necessary, add leading zeros to reach 8 characters
+ let fill = 8usize.saturating_sub(len);
+ Some("0".repeat(fill) + suffix)
+ } else {
+ None
+ }
+}
+
/// A firmware version for a Nitrokey device.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FirmwareVersion {
@@ -58,6 +154,36 @@ impl fmt::Display for FirmwareVersion {
}
}
+/// The status information common to all Nitrokey devices.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct Status {
+ /// The firmware version of the device.
+ pub firmware_version: FirmwareVersion,
+ /// The serial number of the device.
+ pub serial_number: u32,
+ /// The configuration of the device.
+ pub config: Config,
+}
+
+impl From<nitrokey_sys::NK_status> for Status {
+ fn from(status: nitrokey_sys::NK_status) -> Self {
+ Self {
+ firmware_version: FirmwareVersion {
+ major: status.firmware_version_major,
+ minor: status.firmware_version_minor,
+ },
+ serial_number: status.serial_number_smart_card,
+ config: RawConfig {
+ numlock: status.config_numlock,
+ capslock: status.config_capslock,
+ scrollock: status.config_scrolllock,
+ user_password: status.otp_user_password,
+ }
+ .into(),
+ }
+ }
+}
+
/// A Nitrokey device.
///
/// This trait provides the commands that can be executed without authentication and that are
@@ -102,6 +228,35 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
/// # }
fn get_model(&self) -> Model;
+ /// Returns the status of the Nitrokey device.
+ ///
+ /// This methods returns the status information common to all Nitrokey devices as a
+ /// [`Status`][] struct. Some models may provide more information, for example
+ /// [`get_storage_status`][] returns the [`StorageStatus`][] struct.
+ ///
+ /// # Errors
+ ///
+ /// - [`NotConnected`][] if the Nitrokey device has been disconnected
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::Device;
+ ///
+ /// let mut manager = nitrokey::take()?;
+ /// let device = manager.connect()?;
+ /// let status = device.get_status()?;
+ /// println!("Firmware version: {}", status.firmware_version);
+ /// println!("Serial number: {:x}", status.serial_number);
+ /// # Ok::<(), nitrokey::Error>(())
+ /// ```
+ ///
+ /// [`get_storage_status`]: struct.Storage.html#method.get_storage_status
+ /// [`NotConnected`]: enum.CommunicationError.html#variant.NotConnected
+ /// [`Status`]: struct.Status.html
+ /// [`StorageStatus`]: struct.StorageStatus.html
+ fn get_status(&self) -> Result<Status, Error>;
+
/// Returns the serial number of the Nitrokey device. The serial number is the string
/// representation of a hex number.
///
@@ -428,12 +583,8 @@ pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt
}
}
-fn get_connected_model() -> Option<Model> {
- match unsafe { nitrokey_sys::NK_get_device_model() } {
- nitrokey_sys::NK_device_model_NK_PRO => Some(Model::Pro),
- nitrokey_sys::NK_device_model_NK_STORAGE => Some(Model::Storage),
- _ => None,
- }
+fn get_connected_model() -> Result<Model, Error> {
+ Model::try_from(unsafe { nitrokey_sys::NK_get_device_model() })
}
pub(crate) fn create_device_wrapper(
@@ -449,16 +600,40 @@ pub(crate) fn create_device_wrapper(
pub(crate) fn get_connected_device(
manager: &mut crate::Manager,
) -> Result<DeviceWrapper<'_>, Error> {
- match get_connected_model() {
- Some(model) => Ok(create_device_wrapper(manager, model)),
- None => Err(CommunicationError::NotConnected.into()),
- }
+ Ok(create_device_wrapper(manager, get_connected_model()?))
}
pub(crate) fn connect_enum(model: Model) -> bool {
- let model = match model {
- Model::Storage => nitrokey_sys::NK_device_model_NK_STORAGE,
- Model::Pro => nitrokey_sys::NK_device_model_NK_PRO,
- };
- unsafe { nitrokey_sys::NK_login_enum(model) == 1 }
+ unsafe { nitrokey_sys::NK_login_enum(model.into()) == 1 }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::get_hidapi_serial_number;
+
+ #[test]
+ fn hidapi_serial_number() {
+ assert_eq!(None, get_hidapi_serial_number(""));
+ assert_eq!(None, get_hidapi_serial_number("00000000000000000"));
+ assert_eq!(
+ Some("00001234".to_string()),
+ get_hidapi_serial_number("1234")
+ );
+ assert_eq!(
+ Some("00001234".to_string()),
+ get_hidapi_serial_number("00001234")
+ );
+ assert_eq!(
+ Some("00001234".to_string()),
+ get_hidapi_serial_number("000000001234")
+ );
+ assert_eq!(
+ Some("100000001234".to_string()),
+ get_hidapi_serial_number("100000001234")
+ );
+ assert_eq!(
+ Some("10000001234".to_string()),
+ get_hidapi_serial_number("010000001234")
+ );
+ }
}
diff --git a/src/device/pro.rs b/src/device/pro.rs
index a65345e..591b730 100644
--- a/src/device/pro.rs
+++ b/src/device/pro.rs
@@ -3,8 +3,10 @@
use nitrokey_sys;
-use crate::device::{Device, Model};
+use crate::device::{Device, Model, Status};
+use crate::error::Error;
use crate::otp::GenerateOtp;
+use crate::util::get_command_result;
/// A Nitrokey Pro device without user or admin authentication.
///
@@ -74,6 +76,20 @@ impl<'a> Device<'a> for Pro<'a> {
fn get_model(&self) -> Model {
Model::Pro
}
+
+ fn get_status(&self) -> Result<Status, Error> {
+ let mut raw_status = nitrokey_sys::NK_status {
+ firmware_version_major: 0,
+ firmware_version_minor: 0,
+ serial_number_smart_card: 0,
+ config_numlock: 0,
+ config_capslock: 0,
+ config_scrolllock: 0,
+ otp_user_password: false,
+ };
+ get_command_result(unsafe { nitrokey_sys::NK_get_status(&mut raw_status) })?;
+ Ok(raw_status.into())
+ }
}
impl<'a> GenerateOtp for Pro<'a> {}
diff --git a/src/device/storage.rs b/src/device/storage.rs
index 370ce36..deb2844 100644
--- a/src/device/storage.rs
+++ b/src/device/storage.rs
@@ -1,14 +1,16 @@
-// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
+// Copyright (C) 2019-2020 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT
+use std::convert::TryFrom as _;
use std::fmt;
+use std::ops;
use nitrokey_sys;
-use crate::device::{Device, FirmwareVersion, Model};
-use crate::error::Error;
+use crate::device::{Device, FirmwareVersion, Model, Status};
+use crate::error::{CommandError, Error};
use crate::otp::GenerateOtp;
-use crate::util::{get_command_result, get_cstring};
+use crate::util::{get_command_result, get_cstring, get_last_error};
/// A Nitrokey Storage device without user or admin authentication.
///
@@ -141,6 +143,20 @@ pub struct StorageStatus {
pub stick_initialized: bool,
}
+/// The progress of a background operation on the Nitrokey.
+///
+/// Some commands may start a background operation during which no other commands can be executed.
+/// This enum stores the status of a background operation: Ongoing with a relative progress (up to
+/// 100), or idle, i. e. no background operation has been started or the last one has been
+/// finished.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum OperationStatus {
+ /// A background operation with its progress value (less than or equal to 100).
+ Ongoing(u8),
+ /// No backgrund operation.
+ Idle,
+}
+
impl<'a> Storage<'a> {
pub(crate) fn new(manager: &'a mut crate::Manager) -> Storage<'a> {
Storage {
@@ -525,7 +541,7 @@ impl<'a> Storage<'a> {
/// # fn try_main() -> Result<(), Error> {
/// let mut manager = nitrokey::take()?;
/// let device = manager.connect_storage()?;
- /// match device.get_status() {
+ /// match device.get_storage_status() {
/// Ok(status) => {
/// println!("SD card ID: {:#x}", status.serial_number_sd_card);
/// },
@@ -534,7 +550,7 @@ impl<'a> Storage<'a> {
/// # Ok(())
/// # }
/// ```
- pub fn get_status(&self) -> Result<StorageStatus, Error> {
+ pub fn get_storage_status(&self) -> Result<StorageStatus, Error> {
let mut raw_status = nitrokey_sys::NK_storage_status {
unencrypted_volume_read_only: false,
unencrypted_volume_active: false,
@@ -635,11 +651,117 @@ impl<'a> Storage<'a> {
})
}
+ /// Returns a range of the SD card that has not been used to during this power cycle.
+ ///
+ /// The Nitrokey Storage tracks read and write access to the SD card during a power cycle.
+ /// This method returns a range of the SD card that has not been accessed during this power
+ /// cycle. The range is relative to the total size of the SD card, so both values are less
+ /// than or equal to 100. This can be used as a guideline when creating a hidden volume.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// let mut manager = nitrokey::take()?;
+ /// let storage = manager.connect_storage()?;
+ /// let usage = storage.get_sd_card_usage()?;
+ /// println!("SD card usage: {}..{}", usage.start, usage.end);
+ /// # Ok::<(), nitrokey::Error>(())
+ /// ```
+ pub fn get_sd_card_usage(&self) -> Result<ops::Range<u8>, Error> {
+ let mut usage_data = nitrokey_sys::NK_SD_usage_data {
+ write_level_min: 0,
+ write_level_max: 0,
+ };
+ let result = unsafe { nitrokey_sys::NK_get_SD_usage_data(&mut usage_data) };
+ match get_command_result(result) {
+ Ok(_) => {
+ if usage_data.write_level_min > usage_data.write_level_max
+ || usage_data.write_level_max > 100
+ {
+ Err(Error::UnexpectedError)
+ } else {
+ Ok(ops::Range {
+ start: usage_data.write_level_min,
+ end: usage_data.write_level_max,
+ })
+ }
+ }
+ Err(err) => Err(err),
+ }
+ }
+
/// Blinks the red and green LED alternatively and infinitely until the device is reconnected.
pub fn wink(&mut self) -> Result<(), Error> {
get_command_result(unsafe { nitrokey_sys::NK_wink() })
}
+ /// Returns the status of an ongoing background operation on the Nitrokey Storage.
+ ///
+ /// Some commands may start a background operation during which no other commands can be
+ /// executed. This method can be used to check whether such an operation is ongoing.
+ ///
+ /// Currently, this is only used by the [`fill_sd_card`][] method.
+ ///
+ /// [`fill_sd_card`]: #method.fill_sd_card
+ pub fn get_operation_status(&self) -> Result<OperationStatus, Error> {
+ let status = unsafe { nitrokey_sys::NK_get_progress_bar_value() };
+ match status {
+ 0..=100 => u8::try_from(status)
+ .map(OperationStatus::Ongoing)
+ .map_err(|_| Error::UnexpectedError),
+ -1 => Ok(OperationStatus::Idle),
+ -2 => Err(get_last_error()),
+ _ => Err(Error::UnexpectedError),
+ }
+ }
+
+ /// Overwrites the SD card with random data.
+ ///
+ /// Ths method starts a background operation that overwrites the SD card with random data.
+ /// While this operation is ongoing, no other commands can be executed. Use the
+ /// [`get_operation_status`][] function to check the progress of the operation.
+ ///
+ /// # Errors
+ ///
+ /// - [`InvalidString`][] if one of the provided passwords contains a null byte
+ /// - [`WrongPassword`][] if the admin password is wrong
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use nitrokey::OperationStatus;
+ ///
+ /// let mut manager = nitrokey::take()?;
+ /// let mut storage = manager.connect_storage()?;
+ /// storage.fill_sd_card("12345678")?;
+ /// loop {
+ /// match storage.get_operation_status()? {
+ /// OperationStatus::Ongoing(progress) => println!("{}/100", progress),
+ /// OperationStatus::Idle => {
+ /// println!("Done!");
+ /// break;
+ /// }
+ /// }
+ /// }
+ /// # Ok::<(), nitrokey::Error>(())
+ /// ```
+ ///
+ /// [`get_operation_status`]: #method.get_operation_status
+ /// [`InvalidString`]: enum.LibraryError.html#variant.InvalidString
+ /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword
+ pub fn fill_sd_card(&mut self, admin_pin: &str) -> Result<(), Error> {
+ let admin_pin_string = get_cstring(admin_pin)?;
+ get_command_result(unsafe {
+ nitrokey_sys::NK_fill_SD_card_with_random_data(admin_pin_string.as_ptr())
+ })
+ .or_else(|err| match err {
+ // libnitrokey’s C API returns a LongOperationInProgressException with the same error
+ // code as the WrongCrc command error, so we cannot distinguish them.
+ Error::CommandError(CommandError::WrongCrc) => Ok(()),
+ err => Err(err),
+ })
+ }
+
/// Exports the firmware to the unencrypted volume.
///
/// This command requires the admin PIN. The unencrypted volume must be in read-write mode
@@ -678,6 +800,33 @@ impl<'a> Device<'a> for Storage<'a> {
fn get_model(&self) -> Model {
Model::Storage
}
+
+ fn get_status(&self) -> Result<Status, Error> {
+ // Currently, the GET_STATUS command does not report the correct firmware version and
+ // serial number on the Nitrokey Storage, see [0]. Until this is fixed in libnitrokey, we
+ // have to manually execute the GET_DEVICE_STATUS command (get_storage_status) and complete
+ // the missing data, see [1].
+ // [0] https://github.com/Nitrokey/nitrokey-storage-firmware/issues/96
+ // [1] https://github.com/Nitrokey/libnitrokey/issues/166
+
+ let mut raw_status = nitrokey_sys::NK_status {
+ firmware_version_major: 0,
+ firmware_version_minor: 0,
+ serial_number_smart_card: 0,
+ config_numlock: 0,
+ config_capslock: 0,
+ config_scrolllock: 0,
+ otp_user_password: false,
+ };
+ get_command_result(unsafe { nitrokey_sys::NK_get_status(&mut raw_status) })?;
+ let mut status = Status::from(raw_status);
+
+ let storage_status = self.get_storage_status()?;
+ status.firmware_version = storage_status.firmware_version;
+ status.serial_number = storage_status.serial_number_smart_card;
+
+ Ok(status)
+ }
}
impl<'a> GenerateOtp for Storage<'a> {}
diff --git a/src/device/wrapper.rs b/src/device/wrapper.rs
index a3a18f9..69291ad 100644
--- a/src/device/wrapper.rs
+++ b/src/device/wrapper.rs
@@ -1,7 +1,7 @@
// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT
-use crate::device::{Device, Model, Pro, Storage};
+use crate::device::{Device, Model, Pro, Status, Storage};
use crate::error::Error;
use crate::otp::GenerateOtp;
@@ -131,4 +131,11 @@ impl<'a> Device<'a> for DeviceWrapper<'a> {
DeviceWrapper::Storage(_) => Model::Storage,
}
}
+
+ fn get_status(&self) -> Result<Status, Error> {
+ match self {
+ DeviceWrapper::Pro(dev) => dev.get_status(),
+ DeviceWrapper::Storage(dev) => dev.get_status(),
+ }
+ }
}
diff --git a/src/error.rs b/src/error.rs
index 9e6adc0..f9af594 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -28,6 +28,8 @@ pub enum Error {
UnexpectedError,
/// 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),
}
@@ -102,6 +104,7 @@ impl error::Error for Error {
Error::RandError(ref err) => Some(err.as_ref()),
Error::UnexpectedError => None,
Error::UnknownError(_) => None,
+ Error::UnsupportedModelError => None,
Error::Utf8Error(ref err) => Some(err),
}
}
@@ -118,6 +121,7 @@ impl fmt::Display for Error {
Error::RandError(ref err) => write!(f, "RNG error: {}", err),
Error::UnexpectedError => write!(f, "An unexpected error occurred"),
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),
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 059792d..9efad91 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,8 +127,10 @@ 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;
@@ -118,14 +138,16 @@ 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, Model, OperationStatus, Pro, SdCardData, 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 +257,7 @@ impl Manager {
/// # Errors
///
/// - [`NotConnected`][] if no Nitrokey device is connected
+ /// - [`UnsupportedModelError`][] if the Nitrokey device is not supported by this crate
///
/// # Example
///
@@ -252,6 +275,7 @@ impl Manager {
/// ```
///
/// [`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)
@@ -290,6 +314,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
@@ -414,6 +481,71 @@ pub fn force_take() -> Result<sync::MutexGuard<'static, Manager>, Error> {
}
}
+/// 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`][]).