From 22e378677d5b00a05c021dc6660651608b384e0d Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 7 Jun 2018 00:22:45 +0200 Subject: Add support for encrypted volume MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds support for the commands to enable or disable the encrypted volume on the Nitrokey Storage. To test these commands, the output of lsblk is parsed for the device model “Nitrokey Storage”. This is not perfect but seems to be the best solution for automated testing. As the effect of enabling and disabling volumes is not immediate, a delay of two seconds is added to the tests before checking lsblk. This is sufficient on my machine, yet it would be better to have a portable version of this check. This patch also adds a lock method to Device that executes the lock_device command. This command was previously only used to close the password safe. On the Nitrokey Storage, it also disables the encrypted and hidden volume. --- TODO.md | 3 +- src/device.rs | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/tests/device.rs | 50 +++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index 992b501..e2fb3fe 100644 --- a/TODO.md +++ b/TODO.md @@ -4,8 +4,6 @@ - `NK_build_aes_key` - `NK_is_AES_supported` - `NK_send_startup` - - `NK_unlock_encrypted_volume` - - `NK_lock_encrypted_volume` - `NK_unlock_hidden_volume` - `NK_lock_hidden_volume` - `NK_create_hidden_volume` @@ -39,3 +37,4 @@ - Consider implementing `Into` for `(Device, CommandError)` - Check error handling in PasswordSafe::drop(). - Disable creation of multiple password safes at the same time. +- Check timing in Storage tests. diff --git a/src/device.rs b/src/device.rs index d6b9780..f901306 100644 --- a/src/device.rs +++ b/src/device.rs @@ -408,6 +408,30 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { )) } } + + /// Locks the Nitrokey device. + /// + /// This disables the password store if it has been unlocked. On the Nitrokey Storage, this + /// also disables the volumes if they have been enabled. + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::{CommandStatus, Device}; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// match device.lock() { + /// CommandStatus::Success => println!("Locked the Nitrokey device."), + /// CommandStatus::Error(err) => println!("Could not lock the Nitrokey device: {:?}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + fn lock(&self) -> CommandStatus { + unsafe { CommandStatus::from(nitrokey_sys::NK_lock_device()) } + } } /// Connects to a Nitrokey device. This method can be used to connect to any connected device, @@ -509,6 +533,81 @@ impl Storage { false => Err(CommandError::Unknown), } } + + /// Enables the encrypted storage volume. + /// + /// Once the encrypted volume is enabled, it is presented to the operating system as a block + /// device. The API does not provide any information on the name or path of this block device. + /// + /// # Errors + /// + /// - [`InvalidString`][] if the provided password contains a null byte + /// - [`WrongPassword`][] if the provided user password is wrong + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::{CommandStatus}; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::Storage::connect()?; + /// match device.enable_encrypted_volume("123456") { + /// CommandStatus::Success => println!("Enabled the encrypted volume."), + /// CommandStatus::Error(err) => println!("Could not enable the encrypted volume: {:?}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword + pub fn enable_encrypted_volume(&self, user_pin: &str) -> CommandStatus { + let user_pin = CString::new(user_pin); + if user_pin.is_err() { + return CommandStatus::Error(CommandError::InvalidString); + } + let user_pin = user_pin.unwrap(); + unsafe { CommandStatus::from(nitrokey_sys::NK_unlock_encrypted_volume(user_pin.as_ptr())) } + } + + /// Disables the encrypted storage volume. + /// + /// Once the volume is disabled, it can be no longer accessed as a block device. If the + /// encrypted volume has not been enabled, this method still returns a success. + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::{CommandStatus}; + /// # use nitrokey::CommandError; + /// + /// fn use_volume() {} + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::Storage::connect()?; + /// match device.enable_encrypted_volume("123456") { + /// CommandStatus::Success => { + /// println!("Enabled the encrypted volume."); + /// use_volume(); + /// match device.disable_encrypted_volume() { + /// CommandStatus::Success => println!("Disabled the encrypted volume."), + /// CommandStatus::Err(err) => { + /// println!("Could not disable the encrypted volume: {:?}", err); + /// }, + /// }; + /// }, + /// CommandStatus::Error(err) => println!("Could not enable the encrypted volume: {:?}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword + pub fn disable_encrypted_volume(&self) -> CommandStatus { + unsafe { CommandStatus::from(nitrokey_sys::NK_lock_encrypted_volume()) } + } } impl Drop for Storage { diff --git a/src/tests/device.rs b/src/tests/device.rs index 68f1a39..7f7a819 100644 --- a/src/tests/device.rs +++ b/src/tests/device.rs @@ -1,10 +1,24 @@ use std::ffi::CStr; +use std::process::Command; +use std::{thread, time}; use tests::util::{Target, ADMIN_PASSWORD, USER_PASSWORD}; use {Authenticate, CommandError, CommandStatus, Config, Device}; static ADMIN_NEW_PASSWORD: &str = "1234567890"; static USER_NEW_PASSWORD: &str = "abcdefghij"; +fn count_nitrokey_block_devices() -> usize { + thread::sleep(time::Duration::from_secs(2)); + let output = Command::new("lsblk") + .args(&["-o", "MODEL"]) + .output() + .expect("Could not list block devices"); + String::from_utf8_lossy(&output.stdout) + .split("\n") + .filter(|&s| s == "Nitrokey Storage") + .count() +} + #[test] #[cfg_attr(not(feature = "test-no-device"), ignore)] fn connect_no_device() { @@ -227,3 +241,39 @@ fn unlock_user_pin() { ); device.authenticate_user(USER_PASSWORD).unwrap(); } + +#[test] +#[cfg_attr(not(feature = "test-storage"), ignore)] +fn encrypted_volume() { + let device = Target::connect().unwrap(); + assert_eq!(CommandStatus::Success, device.lock()); + + assert_eq!(1, count_nitrokey_block_devices()); + assert_eq!(CommandStatus::Success, device.disable_encrypted_volume()); + assert_eq!(1, count_nitrokey_block_devices()); + assert_eq!( + CommandStatus::Error(CommandError::WrongPassword), + device.enable_encrypted_volume("123") + ); + assert_eq!(1, count_nitrokey_block_devices()); + assert_eq!( + CommandStatus::Success, + device.enable_encrypted_volume(USER_PASSWORD) + ); + assert_eq!(2, count_nitrokey_block_devices()); + assert_eq!(CommandStatus::Success, device.disable_encrypted_volume()); + assert_eq!(1, count_nitrokey_block_devices()); +} + +#[test] +#[cfg_attr(not(feature = "test-storage"), ignore)] +fn lock() { + let device = Target::connect().unwrap(); + + assert_eq!( + CommandStatus::Success, + device.enable_encrypted_volume(USER_PASSWORD) + ); + assert_eq!(CommandStatus::Success, device.lock()); + assert_eq!(1, count_nitrokey_block_devices()); +} -- cgit v1.2.1