aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--nitrocli/CHANGELOG.md4
-rw-r--r--nitrocli/src/crc32.rs88
-rw-r--r--nitrocli/src/error.rs9
-rw-r--r--nitrocli/src/main.rs237
-rw-r--r--nitrocli/src/nitrokey.rs256
5 files changed, 27 insertions, 567 deletions
diff --git a/nitrocli/CHANGELOG.md b/nitrocli/CHANGELOG.md
index 7ff193a..c8985f3 100644
--- a/nitrocli/CHANGELOG.md
+++ b/nitrocli/CHANGELOG.md
@@ -1,7 +1,7 @@
Unreleased
----------
-- Use the `nitrokey` crate for the `open` and `close` commands instead
- of directly communicating with the Nitrokey device
+- Use the `nitrokey` crate for the `open`, `close`, and `status`
+ commands instead of directly communicating with the Nitrokey device
- Added `nitrokey` version `0.2.1` as a direct dependency and
`nitrokey-sys` version `3.4.1` as well as `rand` version `0.4.3` as
indirect dependencies
diff --git a/nitrocli/src/crc32.rs b/nitrocli/src/crc32.rs
deleted file mode 100644
index 8b85b49..0000000
--- a/nitrocli/src/crc32.rs
+++ /dev/null
@@ -1,88 +0,0 @@
-// crc32.rs
-
-// *************************************************************************
-// * Copyright (C) 2017-2018 Daniel Mueller (deso@posteo.net) *
-// * *
-// * This program is free software: you can redistribute it and/or modify *
-// * it under the terms of the GNU General Public License as published by *
-// * the Free Software Foundation, either version 3 of the License, or *
-// * (at your option) any later version. *
-// * *
-// * This program is distributed in the hope that it will be useful, *
-// * but WITHOUT ANY WARRANTY; without even the implied warranty of *
-// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
-// * GNU General Public License for more details. *
-// * *
-// * You should have received a copy of the GNU General Public License *
-// * along with this program. If not, see <http://www.gnu.org/licenses/>. *
-// *************************************************************************
-
-/// Polynomial used in STM32.
-const CRC32_POLYNOMIAL: u32 = 0x04c1_1db7;
-
-
-fn crc32(mut crc: u32, data: u32) -> u32 {
- crc ^= data;
-
- for _ in 0..32 {
- if crc & 0x8000_0000 != 0 {
- crc = (crc << 1) ^ CRC32_POLYNOMIAL;
- } else {
- crc <<= 1;
- }
- }
- crc
-}
-
-
-/// Retrieve a u32 slice of the 'data' part.
-///
-/// Note that the size of the supplied data has to be a multiple of 4
-/// bytes.
-fn as_slice_u32(data: &[u8]) -> &[u32] {
- assert!(data.len() % ::std::mem::size_of::<u32>() == 0);
-
- unsafe {
- let ptr = data.as_ptr() as *const u32;
- let len = data.len() / ::std::mem::size_of::<u32>();
- ::std::slice::from_raw_parts(ptr, len)
- }
-}
-
-
-/// Calculate the CRC of a byte slice.
-pub fn crc(data: &[u8]) -> u32 {
- let mut crc = 0xffff_ffff;
- let data = as_slice_u32(data);
-
- for byte in data {
- crc = crc32(crc, *byte);
- }
- crc
-}
-
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_crc32() {
- let mut crc = 0;
-
- // The expected values were computed with the original function.
- crc = crc32(crc, 0xdeadbeef);
- assert_eq!(crc, 0x46dec763);
-
- crc = crc32(crc, 42);
- assert_eq!(crc, 0x7e579b45);
- }
-
- #[test]
- fn test_crc() {
- let data = &"thisisatextthatistobecrced..".to_string().into_bytes();
- let crc = crc(data);
-
- assert_eq!(crc, 0x469db4ee);
- }
-}
diff --git a/nitrocli/src/error.rs b/nitrocli/src/error.rs
index 5d6b266..80d9d92 100644
--- a/nitrocli/src/error.rs
+++ b/nitrocli/src/error.rs
@@ -26,20 +26,12 @@ use libhid;
#[derive(Debug)]
pub enum Error {
- HidError(libhid::Error),
IoError(io::Error),
Utf8Error(string::FromUtf8Error),
Error(String),
}
-impl From<libhid::Error> for Error {
- fn from(e: libhid::Error) -> Error {
- Error::HidError(e)
- }
-}
-
-
impl From<io::Error> for Error {
fn from(e: io::Error) -> Error {
Error::IoError(e)
@@ -57,7 +49,6 @@ impl From<string::FromUtf8Error> for Error {
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
- Error::HidError(ref e) => write!(f, "hidapi error: {}", e),
Error::Utf8Error(_) => write!(f, "Encountered UTF-8 conversion error"),
Error::IoError(ref e) => write!(f, "IO error: {}", e.get_ref().unwrap()),
Error::Error(ref e) => write!(f, "{}", e),
diff --git a/nitrocli/src/main.rs b/nitrocli/src/main.rs
index 714c573..5ec68a2 100644
--- a/nitrocli/src/main.rs
+++ b/nitrocli/src/main.rs
@@ -68,29 +68,19 @@
//! Nitrocli is a program providing a command line interface to certain
//! commands of the Nitrokey Storage device.
-mod crc32;
mod error;
-mod nitrokey;
mod pinentry;
-use std::mem;
use std::process;
use std::result;
-use std::thread;
-use std::time;
use libnitrokey;
use crate::error::Error;
type Result<T> = result::Result<T, Error>;
-type NitroFunc = dyn Fn(&mut libhid::Handle) -> Result<()>;
-
const PIN_TYPE: pinentry::PinType = pinentry::PinType::User;
-const SEND_TRY_COUNT: i8 = 3;
-const RECV_TRY_COUNT: i8 = 40;
-const SEND_RECV_DELAY_MS: u64 = 200;
/// Create an `error::Error` with an error message of the format `msg: err`.
@@ -105,185 +95,58 @@ fn get_storage_device() -> Result<libnitrokey::Storage> {
.or_else(|_| Err(Error::Error("Nitrokey device not found".to_string())))
}
-
-/// Send a HID feature report to the device represented by the given handle.
-fn send<P>(handle: &mut libhid::Handle, report: &nitrokey::Report<P>) -> Result<()>
- where P: AsRef<[u8]>,
-{
- let mut retry = SEND_TRY_COUNT;
- loop {
- let result = handle.feature().send_to(0, report.as_ref());
- retry -= 1;
-
- match result {
- Ok(_) => {
- return Ok(());
- },
- Err(err) => {
- if retry > 0 {
- thread::sleep(time::Duration::from_millis(SEND_RECV_DELAY_MS));
- continue;
- } else {
- return Err(Error::HidError(err));
- }
- },
- }
- }
-}
-
-
-/// Receive a HID feature report from the device represented by the given handle.
-fn receive<P>(handle: &mut libhid::Handle) -> Result<nitrokey::Report<P>>
- where P: AsRef<[u8]> + Default,
-{
- let mut retry = RECV_TRY_COUNT;
- loop {
- let mut report = nitrokey::Report::<P>::new();
- let result = handle.feature().get_from(0, report.as_mut());
-
- retry -= 1;
-
- match result {
- Ok(size) => {
- if size < mem::size_of_val(&report) {
- if retry > 0 {
- continue;
- } else {
- return Err(Error::Error("Failed to receive complete report".to_string()));
- }
- }
-
- if !report.is_valid() {
- if retry > 0 {
- continue;
- } else {
- return Err(Error::Error("Failed to receive report: CRC mismatch".to_string()));
- }
- }
- return Ok(report);
- },
-
- Err(err) => {
- if retry > 0 {
- thread::sleep(time::Duration::from_millis(SEND_RECV_DELAY_MS));
- continue;
- } else {
- return Err(Error::HidError(err));
- }
- },
+/// Return a string representation of the given volume status.
+fn get_volume_status(status: &libnitrokey::VolumeStatus) -> &'static str {
+ if status.active {
+ if status.read_only {
+ "read-only"
+ } else {
+ "active"
}
+ } else {
+ "inactive"
}
}
-/// Transmit a HID feature report to the nitrokey and receive a response.
-fn transmit<PS, PR>(handle: &mut libhid::Handle,
- report: &nitrokey::Report<PS>)
- -> Result<nitrokey::Report<PR>>
- where PS: AsRef<[u8]>,
- PR: AsRef<[u8]> + Default,
-{
- send(handle, report)?;
-
- // We need to give the stick some time to handle the command. If we
- // don't, we might just receive stale data from before.
- thread::sleep(time::Duration::from_millis(SEND_RECV_DELAY_MS));
-
- receive::<PR>(handle)
-}
-
-
-/// Find and open the nitrokey device and execute a function on it.
-fn nitrokey_do(function: &NitroFunc) -> Result<()> {
- let hid = libhid::init()?;
- // The Manager::find method is plain stupid as it still returns an
- // iterable. Using it does not help in more concise error handling.
- for device in hid.devices() {
- if device.vendor_id() == nitrokey::VID && device.product_id() == nitrokey::PID {
- return function(&mut device.open()?);
- }
- }
- Err(Error::Error("Nitrokey device not found".to_string()))
-}
-
-
/// Pretty print the response of a status command.
-fn print_status(response: &nitrokey::DeviceStatusResponse) {
+fn print_status(status: &libnitrokey::StorageStatus) {
println!("Status:");
// We omit displaying information about the smartcard here as this
// program really is only about the SD card portion of the device.
- println!(" SD card ID: {:#x}", response.active_sdcard_id);
+ println!(" SD card ID: {:#x}", status.serial_number_sd_card);
println!(" firmware version: {}.{}",
- response.version_major,
- response.version_minor);
+ status.firmware_version_major,
+ status.firmware_version_minor);
println!(" firmware: {}",
- if response.firmware_locked != 0 {
+ if status.firmware_locked {
"locked".to_string()
} else {
"unlocked".to_string()
});
println!(" storage keys: {}",
- if response.storage_keys_missing == 0 {
+ if status.stick_initialized {
"created".to_string()
} else {
"not created".to_string()
});
- println!(" user retry count: {}",
- response.user_password_retry_count);
- println!(" admin retry count: {}",
- response.admin_password_retry_count);
+ println!(" user retry count: {}", status.user_retry_count);
+ println!(" admin retry count: {}", status.admin_retry_count);
println!(" volumes:");
- println!(" unencrypted: {}",
- if response.volume_active & nitrokey::VOLUME_ACTIVE_UNENCRYPTED == 0 {
- "inactive"
- } else if response.unencrypted_volume_read_only != 0 {
- "read-only"
- } else {
- "active"
- });
- println!(" encrypted: {}",
- if response.volume_active & nitrokey::VOLUME_ACTIVE_ENCRYPTED == 0 {
- "inactive"
- } else if response.encrypted_volume_read_only != 0 {
- "read-only"
- } else {
- "active"
- });
- println!(" hidden: {}",
- if response.volume_active & nitrokey::VOLUME_ACTIVE_HIDDEN == 0 {
- "inactive"
- } else if response.hidden_volume_read_only != 0 {
- "read-only"
- } else {
- "active"
- });
+ println!(" unencrypted: {}", get_volume_status(&status.unencrypted_volume));
+ println!(" encrypted: {}", get_volume_status(&status.encrypted_volume));
+ println!(" hidden: {}", get_volume_status(&status.hidden_volume));
}
/// Inquire the status of the nitrokey.
fn status() -> Result<()> {
- type Response = nitrokey::Response<nitrokey::DeviceStatusResponse>;
-
- nitrokey_do(&|handle| {
- let payload = nitrokey::DeviceStatusCommand::new();
- let report = nitrokey::Report::from(payload);
-
- let report = transmit::<_, nitrokey::EmptyPayload>(handle, &report)?;
- let response = &AsRef::<Response>::as_ref(&report.data).data;
-
- // TODO: We should probably check the success of the command as
- // well.
- if response.magic != nitrokey::MAGIC_NUMBER_STICK20_CONFIG {
- let error = format!("Status response contains invalid magic: {:#x} \
- (expected: {:#x})",
- response.magic,
- nitrokey::MAGIC_NUMBER_STICK20_CONFIG);
- return Err(Error::Error(error.to_string()));
- }
+ let status = get_storage_device()?
+ .get_status()
+ .map_err(|err| get_error("Getting Storage status failed", &err))?;
- print_status(response);
- Ok(())
- })
+ print_status(&status);
+ Ok(())
}
@@ -380,53 +243,3 @@ fn run() -> i32 {
fn main() {
process::exit(run());
}
-
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn wrong_crc() {
- type Response = nitrokey::Response<nitrokey::DeviceStatusResponse>;
-
- nitrokey_do(&|handle| {
- let payload = nitrokey::DeviceStatusCommand::new();
- let mut report = nitrokey::Report::from(payload);
-
- // We want to verify that we get the correct result (i.e., a
- // report of the CRC mismatch) repeatedly.
- for _ in 0..10 {
- report.crc += 1;
-
- let new_report = transmit::<_, nitrokey::EmptyPayload>(handle, &report)?;
- let response = AsRef::<Response>::as_ref(&new_report.data);
-
- assert_eq!(response.command, nitrokey::Command::GetDeviceStatus);
- assert_eq!(response.command_crc, report.crc);
- assert_eq!(response.command_status, nitrokey::CommandStatus::WrongCrc);
- }
- return Ok(());
- })
- .unwrap();
- }
-
- #[test]
- fn device_status() {
- type Response = nitrokey::Response<nitrokey::DeviceStatusResponse>;
-
- nitrokey_do(&|handle| {
- let payload = nitrokey::DeviceStatusCommand::new();
- let report = nitrokey::Report::from(payload);
-
- let report = transmit::<_, nitrokey::EmptyPayload>(handle, &report)?;
- let response = AsRef::<Response>::as_ref(&report.data);
-
- assert!(response.device_status == nitrokey::StorageStatus::Idle ||
- response.device_status == nitrokey::StorageStatus::Okay);
- assert_eq!(response.data.magic, nitrokey::MAGIC_NUMBER_STICK20_CONFIG);
- return Ok(());
- })
- .unwrap();
- }
-}
diff --git a/nitrocli/src/nitrokey.rs b/nitrocli/src/nitrokey.rs
deleted file mode 100644
index 9f767d6..0000000
--- a/nitrocli/src/nitrokey.rs
+++ /dev/null
@@ -1,256 +0,0 @@
-// nitrokey.rs
-
-// *************************************************************************
-// * Copyright (C) 2017-2018 Daniel Mueller (deso@posteo.net) *
-// * *
-// * This program is free software: you can redistribute it and/or modify *
-// * it under the terms of the GNU General Public License as published by *
-// * the Free Software Foundation, either version 3 of the License, or *
-// * (at your option) any later version. *
-// * *
-// * This program is distributed in the hope that it will be useful, *
-// * but WITHOUT ANY WARRANTY; without even the implied warranty of *
-// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
-// * GNU General Public License for more details. *
-// * *
-// * You should have received a copy of the GNU General Public License *
-// * along with this program. If not, see <http://www.gnu.org/licenses/>. *
-// *************************************************************************
-
-use std::mem;
-
-use crate::crc32::crc;
-
-
-// The Nitrokey Storage vendor ID.
-pub const VID: u16 = 0x20A0;
-// The Nitrokey Storage product ID.
-pub const PID: u16 = 0x4109;
-
-// Magic number identifying a storage response.
-pub const MAGIC_NUMBER_STICK20_CONFIG: u16 = 0x3318;
-
-// Flags indicating whether the respective volume is active or not.
-pub const VOLUME_ACTIVE_UNENCRYPTED: u8 = 0b001;
-pub const VOLUME_ACTIVE_ENCRYPTED: u8 = 0b010;
-pub const VOLUME_ACTIVE_HIDDEN: u8 = 0b100;
-
-
-#[derive(Debug)]
-#[derive(PartialEq)]
-#[repr(u8)]
-pub enum Command {
- // Retrieve the device status.
- GetDeviceStatus = 0x2E,
-}
-
-
-/// A report is the entity we send to the Nitrokey Storage HID.
-///
-/// A report is always 64 bytes in size. The last four bytes comprise a
-/// CRC of the actual payload. Note that when sending or receiving a
-/// report it usually is preceded by a one byte report ID. This report
-/// ID is zero here and not represented in the actual report object in
-/// our design.
-#[repr(packed)]
-pub struct Report<Payload>
- where Payload: AsRef<[u8]>,
-{
- // The actual payload data. A report may encapsulate a command to send
- // to the stick or a response to receive from it.
- pub data: Payload,
- pub crc: u32,
-}
-
-
-impl<P> Report<P>
- where P: AsRef<[u8]> + Default,
-{
- pub fn new() -> Report<P> {
- Report {
- data: P::default(),
- crc: 0,
- }
- }
-
- pub fn is_valid(&self) -> bool {
- self.crc == crc(self.data.as_ref())
- }
-}
-
-
-impl<P> AsRef<[u8]> for Report<P>
- where P: AsRef<[u8]>,
-{
- fn as_ref(&self) -> &[u8] {
- unsafe { mem::transmute::<&Report<P>, &[u8; 64]>(self) }
- }
-}
-
-
-impl<P> From<P> for Report<P>
- where P: AsRef<[u8]>,
-{
- fn from(payload: P) -> Report<P> {
- let crc = crc(payload.as_ref());
- Report {
- data: payload,
- crc: crc,
- }
- }
-}
-
-
-impl<P> AsMut<[u8]> for Report<P>
- where P: AsRef<[u8]>,
-{
- fn as_mut(&mut self) -> &mut [u8] {
- unsafe { mem::transmute::<&mut Report<P>, &mut [u8; 64]>(self) }
- }
-}
-
-
-pub struct EmptyPayload {
- pub data: [u8; 60],
-}
-
-impl Default for EmptyPayload {
- fn default() -> EmptyPayload {
- EmptyPayload {
- data: [0u8; 60],
- }
- }
-}
-
-impl AsRef<[u8]> for EmptyPayload {
- fn as_ref(&self) -> &[u8] {
- unsafe { mem::transmute::<&EmptyPayload, &[u8; 60]>(self) }
- }
-}
-
-impl<P> AsRef<Response<P>> for EmptyPayload {
- fn as_ref(&self) -> &Response<P> {
- unsafe { mem::transmute::<&EmptyPayload, &Response<P>>(self) }
- }
-}
-
-
-macro_rules! defaultCommandType {
- ( $name:ident ) => {
- #[allow(dead_code)]
- #[repr(packed)]
- pub struct $name {
- command: Command,
- padding: [u8; 59],
- }
- }
-}
-
-macro_rules! defaultCommandNew {
- ( $name:ident, $command:ident ) => {
- impl $name {
- pub fn new() -> $name {
- $name{
- command: Command::$command,
- padding: [0; 59],
- }
- }
- }
- }
-}
-
-macro_rules! defaultPayloadAsRef {
- ( $name:ty ) => {
- impl AsRef<[u8]> for $name {
- fn as_ref(&self) -> &[u8] {
- unsafe { mem::transmute::<&$name, &[u8; 60]>(self) }
- }
- }
- }
-}
-
-macro_rules! defaultCommand {
- ( $name:ident, $command:ident ) => {
- defaultCommandType!($name);
- defaultCommandNew!($name, $command);
- defaultPayloadAsRef!($name);
- }
-}
-
-
-defaultCommand!(DeviceStatusCommand, GetDeviceStatus);
-
-
-#[allow(dead_code)]
-#[derive(Debug)]
-#[derive(PartialEq)]
-#[repr(u8)]
-pub enum CommandStatus {
- Okay = 0,
- WrongCrc = 1,
- WrongSlot = 2,
- SlotNotProgrammed = 3,
- WrongPassword = 4,
- NotAuthorized = 5,
- TimestampWarning = 6,
- NoNameError = 7,
-}
-
-
-#[allow(dead_code)]
-#[derive(Copy)]
-#[derive(Clone)]
-#[derive(Debug)]
-#[derive(PartialEq)]
-#[repr(u8)]
-pub enum StorageStatus {
- Idle = 0,
- Okay = 1,
- Busy = 2,
- WrongPassword = 3,
- BusyProgressbar = 4,
- PasswordMatrixReady = 5,
- NoUserPasswordUnlock = 6,
- SmartcardError = 7,
- SecurityBitActive = 8,
-}
-
-
-#[repr(packed)]
-pub struct Response<Payload> {
- pub device_status: StorageStatus,
- pub command: Command,
- pub command_crc: u32,
- pub command_status: CommandStatus,
- pub data: Payload,
-}
-
-impl<P> AsRef<[u8]> for Response<P> {
- fn as_ref(&self) -> &[u8] {
- unsafe { mem::transmute::<&Response<P>, &[u8; 60]>(self) }
- }
-}
-
-
-#[repr(packed)]
-pub struct DeviceStatusResponse {
- pub padding0: [u8; 22],
- pub magic: u16,
- pub unencrypted_volume_read_only: u8,
- pub encrypted_volume_read_only: u8,
- pub version_major: u8,
- pub version_minor: u8,
- pub version_build: u8,
- pub version_internal: u8,
- pub hidden_volume_read_only: u8,
- pub firmware_locked: u8,
- pub new_sdcard_found: u8,
- pub sdcard_fill_with_random: u8,
- pub active_sdcard_id: u32,
- pub volume_active: u8,
- pub new_smartcard_found: u8,
- pub user_password_retry_count: u8,
- pub admin_password_retry_count: u8,
- pub active_smartcard_id: u32,
- pub storage_keys_missing: u8,
-}