mod librem;
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;
use crate::error::{CommunicationError, Error, LibraryError};
use crate::otp::GenerateOtp;
use crate::pws::GetPasswordSafe;
use crate::util::{
get_command_result, get_cstring, get_struct, owned_str_from_ptr, result_or_error,
};
pub use librem::Librem;
pub use pro::Pro;
pub use storage::{
OperationStatus, SdCardData, Storage, StorageProductionInfo, StorageStatus, VolumeMode,
VolumeStatus,
};
pub use wrapper::DeviceWrapper;
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub enum Model {
Librem,
Storage,
Pro,
}
impl fmt::Display for Model {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match *self {
Model::Librem => "Librem Key",
Model::Pro => "Nitrokey Pro",
Model::Storage => "Nitrokey Storage",
})
}
}
impl From<Model> for nitrokey_sys::NK_device_model {
fn from(model: Model) -> Self {
match model {
Model::Librem => nitrokey_sys::NK_device_model_NK_LIBREM,
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_LIBREM => Ok(Model::Librem),
nitrokey_sys::NK_device_model_NK_PRO => Ok(Model::Pro),
nitrokey_sys::NK_device_model_NK_STORAGE => Ok(Model::Storage),
_ => Err(Error::UnsupportedModelError),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SerialNumber {
value: u32,
}
impl SerialNumber {
pub fn empty() -> Self {
SerialNumber::new(0)
}
fn new(value: u32) -> Self {
SerialNumber { value }
}
#[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;
fn from_str(s: &str) -> Result<SerialNumber, Error> {
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())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct DeviceInfo {
pub model: Option<Model>,
pub path: String,
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) => f.write_str(&model.to_string())?,
None => f.write_str("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"),
}
}
}
fn get_hidapi_serial_number(serial_number: &str) -> Option<SerialNumber> {
let len = serial_number.len();
if len < 8 {
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 {
serial_number.split_at(len - 8).1
} else {
serial_number.split_at(8).0
};
substr.parse().ok()
} else {
None
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FirmwareVersion {
pub major: u8,
pub minor: u8,
}
impl fmt::Display for FirmwareVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "v{}.{}", self.major, self.minor)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Status {
pub firmware_version: FirmwareVersion,
pub serial_number: SerialNumber,
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: Config::from(&status),
}
}
}
pub trait Device<'a>: Authenticate<'a> + GetPasswordSafe<'a> + GenerateOtp + fmt::Debug {
fn into_manager(self) -> &'a mut crate::Manager;
fn get_model(&self) -> Model;
fn get_status(&self) -> Result<Status, Error>;
fn get_serial_number(&self) -> Result<SerialNumber, Error> {
let serial_number = unsafe { nitrokey_sys::NK_device_serial_number_as_u32() };
result_or_error(SerialNumber::new(serial_number))
}
fn get_user_retry_count(&self) -> Result<u8, Error> {
result_or_error(unsafe { nitrokey_sys::NK_get_user_retry_count() })
}
fn get_admin_retry_count(&self) -> Result<u8, Error> {
result_or_error(unsafe { nitrokey_sys::NK_get_admin_retry_count() })
}
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 })
}
fn get_config(&self) -> Result<Config, Error> {
get_struct(|out| unsafe { nitrokey_sys::NK_read_config_struct(out) })
}
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())
})
}
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())
})
}
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(),
)
})
}
fn lock(&mut self) -> Result<(), Error> {
get_command_result(unsafe { nitrokey_sys::NK_lock_device() })
}
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()) })
}
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::Librem => Librem::new(manager).into(),
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");
}
}