diff options
-rw-r--r-- | nitrocli/CHANGELOG.md | 7 | ||||
-rw-r--r-- | nitrocli/src/main.rs | 130 | ||||
-rw-r--r-- | nitrocli/src/nitrokey.rs | 83 |
3 files changed, 54 insertions, 166 deletions
diff --git a/nitrocli/CHANGELOG.md b/nitrocli/CHANGELOG.md index bfe1ffa..7ff193a 100644 --- a/nitrocli/CHANGELOG.md +++ b/nitrocli/CHANGELOG.md @@ -1,7 +1,10 @@ Unreleased ---------- -- 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 +- Use the `nitrokey` crate for the `open` and `close` 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 0.1.3 diff --git a/nitrocli/src/main.rs b/nitrocli/src/main.rs index 23370ee..714c573 100644 --- a/nitrocli/src/main.rs +++ b/nitrocli/src/main.rs @@ -79,6 +79,8 @@ use std::result; use std::thread; use std::time; +use libnitrokey; + use crate::error::Error; type Result<T> = result::Result<T, Error>; @@ -91,6 +93,19 @@ 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`. +fn get_error(msg: &str, err: &libnitrokey::CommandError) -> Error { + Error::Error(format!("{}: {:?}", msg, err)) +} + + +/// Connect to a Nitrokey Storage device and return it. +fn get_storage_device() -> Result<libnitrokey::Storage> { + libnitrokey::Storage::connect() + .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]>, @@ -272,64 +287,35 @@ fn status() -> Result<()> { } -/// Poll the nitrokey until it reports to no longer be busy. -fn wait(handle: &mut libhid::Handle) -> Result<nitrokey::StorageStatus> { - type Response = nitrokey::Response<nitrokey::StorageResponse>; - - loop { - thread::sleep(time::Duration::from_millis(SEND_RECV_DELAY_MS)); - - let report = receive::<nitrokey::EmptyPayload>(handle)?; - let response = AsRef::<Response>::as_ref(&report.data); - let status = response.data.storage_status; - - if status != nitrokey::StorageStatus::Busy && - status != nitrokey::StorageStatus::BusyProgressbar { - return Ok(status); - } - } -} - - /// Open the encrypted volume on the nitrokey. fn open() -> Result<()> { - type Response = nitrokey::Response<nitrokey::StorageResponse>; - - nitrokey_do(&|handle| { - let mut retry = 3; - let mut error_msg: Option<&str> = None; - loop { - let passphrase = pinentry::inquire_passphrase(PIN_TYPE, error_msg)?; - let payload = nitrokey::EnableEncryptedVolumeCommand::new(&passphrase); - let report = nitrokey::Report::from(payload); + let device = get_storage_device()?; - let report = transmit::<_, nitrokey::EmptyPayload>(handle, &report)?; - let response = AsRef::<Response>::as_ref(&report.data); - let mut status = response.data.storage_status; - - if status == nitrokey::StorageStatus::WrongPassword { - pinentry::clear_passphrase(PIN_TYPE)?; - retry -= 1; + let mut retry = 3; + let mut error_msg: Option<&str> = None; + loop { + // TODO: Rethink the usage of String::from_utf8_lossy here. We may + // not want to silently modify the password! + let passphrase = pinentry::inquire_passphrase(PIN_TYPE, error_msg)?; + let passphrase = String::from_utf8_lossy(&passphrase); + match device.enable_encrypted_volume(&passphrase) { + Ok(()) => return Ok(()), + Err(err) => match err { + libnitrokey::CommandError::WrongPassword => { + pinentry::clear_passphrase(PIN_TYPE)?; + retry -= 1; - if retry > 0 { - error_msg = Some("Wrong password, please reenter"); - continue; - } - let error = "Opening encrypted volume failed: Wrong password"; - return Err(Error::Error(error.to_string())); - } - if status == nitrokey::StorageStatus::Busy || - status == nitrokey::StorageStatus::BusyProgressbar { - status = wait(handle)?; - } - if status != nitrokey::StorageStatus::Okay && status != nitrokey::StorageStatus::Idle { - let status = format!("{:?}", status); - let error = format!("Opening encrypted volume failed: {}", status); - return Err(Error::Error(error)); - } - return Ok(()); - } - }) + if retry > 0 { + error_msg = Some("Wrong password, please reenter"); + continue; + } + let error = "Opening encrypted volume failed: Wrong password"; + return Err(Error::Error(error.to_string())); + }, + err => return Err(get_error("Opening encrypted volume failed", &err)), + }, + }; + } } @@ -340,33 +326,15 @@ extern "C" { /// Close the previously opened encrypted volume. fn close() -> Result<()> { - type Response = nitrokey::Response<nitrokey::StorageResponse>; - - nitrokey_do(&|handle| { - // Flush all filesystem caches to disk. We are mostly interested in - // making sure that the encrypted volume on the nitrokey we are - // about to close is not closed while not all data was written to - // it. - unsafe { sync() }; - - let payload = nitrokey::DisableEncryptedVolumeCommand::new(); - let report = nitrokey::Report::from(payload); - - let report = transmit::<_, nitrokey::EmptyPayload>(handle, &report)?; - let response = AsRef::<Response>::as_ref(&report.data); - let mut status = response.data.storage_status; - - if status == nitrokey::StorageStatus::Busy || - status == nitrokey::StorageStatus::BusyProgressbar { - status = wait(handle)?; - } - if status != nitrokey::StorageStatus::Okay && status != nitrokey::StorageStatus::Idle { - let status = format!("{:?}", status); - let error = format!("Closing encrypted volume failed: {}", status); - return Err(Error::Error(error)); - } - Ok(()) - }) + // Flush all filesystem caches to disk. We are mostly interested in + // making sure that the encrypted volume on the nitrokey we are + // about to close is not closed while not all data was written to + // it. + unsafe { sync() }; + + get_storage_device()? + .disable_encrypted_volume() + .map_err(|err| get_error("Closing encrypted volume failed", &err)) } diff --git a/nitrocli/src/nitrokey.rs b/nitrocli/src/nitrokey.rs index 64685e2..9f767d6 100644 --- a/nitrocli/src/nitrokey.rs +++ b/nitrocli/src/nitrokey.rs @@ -17,7 +17,6 @@ // * along with this program. If not, see <http://www.gnu.org/licenses/>. * // ************************************************************************* -use std::cmp; use std::mem; use crate::crc32::crc; @@ -41,10 +40,6 @@ pub const VOLUME_ACTIVE_HIDDEN: u8 = 0b100; #[derive(PartialEq)] #[repr(u8)] pub enum Command { - // The command to enable the encrypted volume. - EnableEncryptedVolume = 0x20, - // The command to disable the encrypted volume. - DisableEncryptedVolume = 0x21, // Retrieve the device status. GetDeviceStatus = 0x2E, } @@ -183,39 +178,6 @@ macro_rules! defaultCommand { } -#[allow(dead_code)] -#[repr(packed)] -pub struct EnableEncryptedVolumeCommand { - command: Command, - // The kind of password. Unconditionally 'P' because the User PIN is - // used to enable the encrypted volume. - kind: u8, - // The password has a maximum length of twenty characters. - password: [u8; 20], - padding: [u8; 38], -} - - -impl EnableEncryptedVolumeCommand { - pub fn new(password: &[u8]) -> EnableEncryptedVolumeCommand { - let mut report = EnableEncryptedVolumeCommand { - command: Command::EnableEncryptedVolume, - kind: b'P', - password: [0; 20], - padding: [0; 38], - }; - - debug_assert!(password.len() <= report.password.len()); - - let len = cmp::min(report.password.len(), password.len()); - report.password[..len].copy_from_slice(&password[..len]); - report - } -} - -defaultPayloadAsRef!(EnableEncryptedVolumeCommand); - -defaultCommand!(DisableEncryptedVolumeCommand, DisableEncryptedVolume); defaultCommand!(DeviceStatusCommand, GetDeviceStatus); @@ -271,17 +233,6 @@ impl<P> AsRef<[u8]> for Response<P> { #[repr(packed)] -pub struct StorageResponse { - pub padding1: [u8; 13], - pub command_counter: u8, - pub last_storage_command: Command, - pub storage_status: StorageStatus, - pub progress: u8, - pub padding2: [u8; 2], -} - - -#[repr(packed)] pub struct DeviceStatusResponse { pub padding0: [u8; 22], pub magic: u16, @@ -303,37 +254,3 @@ pub struct DeviceStatusResponse { pub active_smartcard_id: u32, pub storage_keys_missing: u8, } - - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn encrypted_volume_report() { - let password = "test42".to_string().into_bytes(); - let report = EnableEncryptedVolumeCommand::new(&password); - let expected = ['t' as u8, 'e' as u8, 's' as u8, 't' as u8, '4' as u8, '2' as u8, 0u8, 0u8, - 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8]; - assert_eq!(report.password, expected); - } - - #[test] - #[cfg(debug)] - #[should_panic(expected = "assertion failed")] - fn overly_long_password() { - let password = "012345678912345678901".to_string().into_bytes(); - EnableEncryptedVolumeCommand::new(&password); - } - - #[test] - fn report_crc() { - let password = "passphrase".to_string().into_bytes(); - let payload = EnableEncryptedVolumeCommand::new(&password); - let report = Report::from(payload); - - // The expected checksum was computed using the original - // functionality. - assert_eq!(report.crc, 0xeeb583c); - } -} |