aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Mueller <deso@posteo.net>2017-03-30 21:25:59 -0700
committerDaniel Mueller <deso@posteo.net>2017-03-30 21:25:59 -0700
commit02d31ed3dc89528f5b46201abb0c03b6ef29cc16 (patch)
tree09db53fefb0729ae633c5b9ecada8ecfba965a69
parentc3b61ebe688e6e353b0fcd737f76851f78b8c74f (diff)
downloadnitrocli-02d31ed3dc89528f5b46201abb0c03b6ef29cc16.tar.gz
nitrocli-02d31ed3dc89528f5b46201abb0c03b6ef29cc16.tar.bz2
Receive command responses
By just sending a command to the nitrokey alone we have no idea of what actually happened on the side of the nitrokey. A command could simply be invalid in the current context or the stick could be busy or in a failure state. In order to determine the success of an operation, this change adds the logic to retrieve the response from the nitrokey as well.
-rw-r--r--nitrocli/Makefile27
-rw-r--r--nitrocli/src/main.rs88
-rw-r--r--nitrocli/src/nitrokey.rs128
3 files changed, 241 insertions, 2 deletions
diff --git a/nitrocli/Makefile b/nitrocli/Makefile
new file mode 100644
index 0000000..ca3a0b9
--- /dev/null
+++ b/nitrocli/Makefile
@@ -0,0 +1,27 @@
+# Makefile
+
+#/***************************************************************************
+# * Copyright (C) 2017 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/>. *
+# ***************************************************************************/
+
+
+# We do not want to run the tests concurrently as the Nitrokey seems to
+# have problems with that. We also do not want to introduce additional
+# locking because in the normal program work flow we will not have
+# multiple requests issued in parallel.
+.PHONY: test
+test:
+ @RUST_TEST_NOCAPTURE=1 RUST_TEST_THREADS=1 cargo test
diff --git a/nitrocli/src/main.rs b/nitrocli/src/main.rs
index 700204d..a1f026e 100644
--- a/nitrocli/src/main.rs
+++ b/nitrocli/src/main.rs
@@ -34,11 +34,35 @@ mod pinentry;
use error::Error;
use std::process;
use std::result;
+use std::thread;
+use std::time;
type Result<T> = result::Result<T, Error>;
type NitroFunc = Fn(&mut libhid::Handle) -> Result<()>;
+const SEND_RECV_DELAY_MS: u64 = 200;
+
+
+/// 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]>,
+{
+ handle.feature().send_to(0, report.as_ref())?;
+ return Ok(());
+}
+
+
+/// 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 report = nitrokey::Report::<P>::new();
+ handle.feature().get_from(0, report.as_mut())?;
+ return Ok(report);
+}
+
+
/// Find and open the nitrokey device and execute a function on it.
fn nitrokey_do(function: &NitroFunc) -> Result<()> {
let hid = libhid::init()?;
@@ -60,7 +84,12 @@ fn open() -> Result<()> {
let payload = nitrokey::EnableEncryptedVolumeCommand::new(&passphrase);
let report = nitrokey::Report::from(payload);
- handle.feature().send_to(0, report.as_ref())?;
+ 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::<nitrokey::EmptyPayload>(handle)?;
return Ok(());
});
}
@@ -72,7 +101,7 @@ fn close() -> Result<()> {
let payload = nitrokey::DisableEncryptedVolumeCommand::new();
let report = nitrokey::Report::from(payload);
- handle.feature().send_to(0, report.as_ref())?;
+ send(handle, &report)?;
return Ok(());
});
}
@@ -114,3 +143,58 @@ fn run() -> i32 {
fn main() {
process::exit(run());
}
+
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn wrong_crc() {
+ 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;
+ send(handle, &report).unwrap();
+ thread::sleep(time::Duration::from_millis(SEND_RECV_DELAY_MS));
+
+ let new_report = receive::<nitrokey::EmptyPayload>(handle).unwrap();
+ assert!(new_report.is_valid());
+
+ let response: &nitrokey::Response<nitrokey::DeviceStatusResponse> = new_report.data
+ .as_ref();
+ 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() {
+ nitrokey_do(&|handle| {
+ let payload = nitrokey::DeviceStatusCommand::new();
+ let report = nitrokey::Report::from(payload);
+
+ send(handle, &report).unwrap();
+ thread::sleep(time::Duration::from_millis(SEND_RECV_DELAY_MS));
+
+ let new_report = receive::<nitrokey::EmptyPayload>(handle).unwrap();
+ assert!(new_report.is_valid());
+
+ let response: &nitrokey::Response<nitrokey::DeviceStatusResponse> = new_report.data.as_ref();
+
+ 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
index 37ba597..87283c0 100644
--- a/nitrocli/src/nitrokey.rs
+++ b/nitrocli/src/nitrokey.rs
@@ -27,6 +27,9 @@ 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;
+
#[derive(Debug)]
#[derive(PartialEq)]
@@ -36,6 +39,8 @@ pub enum Command {
EnableEncryptedVolume = 0x20,
// The command to disable the encrypted volume.
DisableEncryptedVolume = 0x21,
+ // Retrieve the device status.
+ GetDeviceStatus = 0x2E,
}
@@ -57,6 +62,22 @@ pub struct Report<Payload>
}
+impl<P> Report<P>
+ where P: AsRef<[u8]> + Default,
+{
+ pub fn new() -> Report<P> {
+ return Report {
+ data: P::default(),
+ crc: 0,
+ };
+ }
+
+ pub fn is_valid(&self) -> bool {
+ return self.crc == crc(self.data.as_ref());
+ }
+}
+
+
impl<P> AsRef<[u8]> for Report<P>
where P: AsRef<[u8]>,
{
@@ -79,6 +100,40 @@ impl<P> From<P> for Report<P>
}
+impl<P> AsMut<[u8]> for Report<P>
+ where P: AsRef<[u8]>,
+{
+ fn as_mut(&mut self) -> &mut [u8] {
+ unsafe { return mem::transmute::<&mut Report<P>, &mut [u8; 64]>(self) };
+ }
+}
+
+
+pub struct EmptyPayload {
+ pub data: [u8; 60],
+}
+
+impl Default for EmptyPayload {
+ fn default() -> EmptyPayload {
+ return EmptyPayload {
+ data: [0u8; 60],
+ };
+ }
+}
+
+impl AsRef<[u8]> for EmptyPayload {
+ fn as_ref(&self) -> &[u8] {
+ unsafe { return mem::transmute::<&EmptyPayload, &[u8; 60]>(self) };
+ }
+}
+
+impl<P> AsRef<Response<P>> for EmptyPayload {
+ fn as_ref(&self) -> &Response<P> {
+ unsafe { return mem::transmute::<&EmptyPayload, &Response<P>>(self) };
+ }
+}
+
+
macro_rules! defaultCommandType {
( $name:ident ) => {
#[allow(dead_code)]
@@ -157,6 +212,79 @@ impl EnableEncryptedVolumeCommand {
defaultPayloadAsRef!(EnableEncryptedVolumeCommand);
defaultCommand!(DisableEncryptedVolumeCommand, DisableEncryptedVolume);
+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(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 { return 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 padding1: u8,
+ pub version_major: u8,
+ pub padding2: u8,
+ pub version_minor: 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,
+}
#[cfg(test)]