diff options
-rw-r--r-- | nitrocli/CHANGELOG.md | 1 | ||||
-rw-r--r-- | nitrocli/Cargo.lock | 4 | ||||
-rw-r--r-- | nitrocli/Cargo.toml | 2 | ||||
-rw-r--r-- | nitrocli/src/commands.rs | 11 | ||||
-rw-r--r-- | nitrokey/CHANGELOG.md | 13 | ||||
-rw-r--r-- | nitrokey/Cargo.toml | 4 | ||||
-rw-r--r-- | nitrokey/TODO.md | 5 | ||||
-rw-r--r-- | nitrokey/src/auth.rs | 5 | ||||
-rw-r--r-- | nitrokey/src/device.rs | 153 | ||||
-rw-r--r-- | nitrokey/src/otp.rs | 33 | ||||
-rw-r--r-- | nitrokey/src/pws.rs | 24 | ||||
-rw-r--r-- | nitrokey/src/util.rs | 70 | ||||
-rw-r--r-- | nitrokey/tests/device.rs | 118 | ||||
-rw-r--r-- | nitrokey/tests/otp.rs | 12 | ||||
-rw-r--r-- | nitrokey/tests/pws.rs | 18 | ||||
-rw-r--r-- | nitrokey/tests/util/mod.rs | 1 |
16 files changed, 390 insertions, 84 deletions
diff --git a/nitrocli/CHANGELOG.md b/nitrocli/CHANGELOG.md index 41cfd91..e32123e 100644 --- a/nitrocli/CHANGELOG.md +++ b/nitrocli/CHANGELOG.md @@ -3,6 +3,7 @@ Unreleased - Added the `-v`/`--verbose` option to control libnitrokey log level - Added the `-m`/`--model` option to restrict connections to a device model +- Bumped `nitrokey` dependency to `0.3.0` 0.2.1 diff --git a/nitrocli/Cargo.lock b/nitrocli/Cargo.lock index 01f74f4..5308712 100644 --- a/nitrocli/Cargo.lock +++ b/nitrocli/Cargo.lock @@ -55,12 +55,12 @@ version = "0.2.1" dependencies = [ "argparse 0.2.2", "libc 0.2.45", - "nitrokey 0.2.3", + "nitrokey 0.3.0", ] [[package]] name = "nitrokey" -version = "0.2.3" +version = "0.3.0" dependencies = [ "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", "nitrokey-sys 3.4.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/nitrocli/Cargo.toml b/nitrocli/Cargo.toml index 9c1bc23..0fc6482 100644 --- a/nitrocli/Cargo.toml +++ b/nitrocli/Cargo.toml @@ -50,7 +50,7 @@ version = "0.2" path = "../libc" [dependencies.nitrokey] -version = "0.2.1" +version = "0.3" path = "../nitrokey" diff --git a/nitrocli/src/commands.rs b/nitrocli/src/commands.rs index e125f17..ac2bbf1 100644 --- a/nitrocli/src/commands.rs +++ b/nitrocli/src/commands.rs @@ -411,10 +411,13 @@ pub fn otp_get( let device = get_device(ctx)?; if algorithm == args::OtpAlgorithm::Totp { device - .set_time(match time { - Some(time) => time, - None => get_unix_timestamp()?, - }) + .set_time( + match time { + Some(time) => time, + None => get_unix_timestamp()?, + }, + true, + ) .map_err(|err| get_error("Could not set time", err))?; } let config = device diff --git a/nitrokey/CHANGELOG.md b/nitrokey/CHANGELOG.md index 5064d4f..ae49683 100644 --- a/nitrokey/CHANGELOG.md +++ b/nitrokey/CHANGELOG.md @@ -1,3 +1,16 @@ +# v0.3.0 (2019-01-04) +- Add a `force` argument to `ConfigureOtp::set_time`. +- Remove the obsolete `CommandError::RngError`. +- Add `CommandError::Undefined` to represent errors without further + information (e. g. a method returned `NULL` unexpectedly). +- Add error code to `CommandError::Unknown`. +- Add the `Storage::change_update_pin` method that changes the firmware update + PIN. +- Add the `Device::factory_reset` method that performs a factory reset. +- Add the `Device::build_aes_key` method that builds a new AES key on the Nitrokey. +- Add the `Storage::enable_firmware_update` method that puts the Nitrokey + Storage in update mode so that the firmware can be updated. + # v0.2.3 (2018-12-31) - Dummy release to fix an issue with the crates.io tarball. diff --git a/nitrokey/Cargo.toml b/nitrokey/Cargo.toml index 6369fba..b8f30db 100644 --- a/nitrokey/Cargo.toml +++ b/nitrokey/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nitrokey" -version = "0.2.3" +version = "0.3.0" authors = ["Robin Krahl <robin.krahl@ireas.org>"] edition = "2018" homepage = "https://code.ireas.org/nitrokey-rs/" @@ -18,5 +18,5 @@ test-storage = [] [dependencies] libc = "0.2" -nitrokey-sys = "3.4.1" +nitrokey-sys = "3.4" rand = "0.6" diff --git a/nitrokey/TODO.md b/nitrokey/TODO.md index 6086ad8..111105d 100644 --- a/nitrokey/TODO.md +++ b/nitrokey/TODO.md @@ -1,7 +1,5 @@ - Add support for the currently unsupported commands: - `NK_set_unencrypted_volume_rorw_pin_type_user` - - `NK_factory_reset` - - `NK_build_aes_key` - `NK_is_AES_supported` - `NK_send_startup` - `NK_unlock_hidden_volume` @@ -13,11 +11,9 @@ - `NK_set_unencrypted_read_write_admin` - `NK_set_encrypted_read_only` - `NK_set_encrypted_read_write` - - `NK_enable_firmware_update` - `NK_export_firmware` - `NK_clear_new_sd_card_warning` - `NK_fill_SD_card_with_random_data` - - `NK_change_update_password` - `NK_get_SD_usage_data_as_string` - `NK_get_progress_bar_value` - `NK_list_devices_by_cpuID` @@ -27,7 +23,6 @@ - `NK_get_major_library_version` - `NK_get_minor_libray_version` - `NK_get_storage_production_info` - - `NK_totp_set_time_soft` - `NK_wink` - Fix timing issues with the `totp_no_pin` and `totp_pin` test cases. - Clear passwords from memory. diff --git a/nitrokey/src/auth.rs b/nitrokey/src/auth.rs index 017cdbb..a129bd8 100644 --- a/nitrokey/src/auth.rs +++ b/nitrokey/src/auth.rs @@ -149,10 +149,7 @@ where A: AuthenticatedDevice<D>, T: Fn(*const i8, *const i8) -> c_int, { - let temp_password = match generate_password(TEMPORARY_PASSWORD_LENGTH) { - Ok(pw) => pw, - Err(_) => return Err((device, CommandError::RngError)), - }; + let temp_password = generate_password(TEMPORARY_PASSWORD_LENGTH); let password = match get_cstring(password) { Ok(password) => password, Err(err) => return Err((device, err)), diff --git a/nitrokey/src/device.rs b/nitrokey/src/device.rs index 9c6608d..78d0d82 100644 --- a/nitrokey/src/device.rs +++ b/nitrokey/src/device.rs @@ -510,6 +510,74 @@ pub trait Device: Authenticate + GetPasswordSafe + GenerateOtp { fn lock(&self) -> Result<(), CommandError> { unsafe { get_command_result(nitrokey_sys::NK_lock_device()) } } + + /// Performs a factory reset on the Nitrokey device. + /// + /// This commands performs a factory reset on the smart card (like the factory reset via `gpg + /// --card-edit`) and then clears the flash memory (password safe, one-time passwords etc.). + /// After a factory reset, [`build_aes_key`][] has to be called before the password safe or the + /// encrypted volume can be used. + /// + /// # Errors + /// + /// - [`InvalidString`][] if the provided password contains a null byte + /// - [`WrongPassword`][] if the admin password is wrong + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// match device.factory_reset("12345678") { + /// Ok(()) => println!("Performed a factory reset."), + /// Err(err) => println!("Could not perform a factory reset: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`build_aes_key`]: #method.build_aes_key + fn factory_reset(&self, admin_pin: &str) -> Result<(), CommandError> { + let admin_pin_string = get_cstring(admin_pin)?; + unsafe { get_command_result(nitrokey_sys::NK_factory_reset(admin_pin_string.as_ptr())) } + } + + /// Builds a new AES key on the Nitrokey. + /// + /// The AES key is used to encrypt the password safe and the encrypted volume. You may need + /// to call this method after a factory reset, either using [`factory_reset`][] or using `gpg + /// --card-edit`. You can also use it to destory the data stored in the password safe or on + /// the encrypted volume. + /// + /// # Errors + /// + /// - [`InvalidString`][] if the provided password contains a null byte + /// - [`WrongPassword`][] if the admin password is wrong + /// + /// # Example + /// + /// ```no_run + /// use nitrokey::Device; + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::connect()?; + /// match device.build_aes_key("12345678") { + /// Ok(()) => println!("New AES keys have been built."), + /// Err(err) => println!("Could not build new AES keys: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`factory_reset`]: #method.factory_reset + fn build_aes_key(&self, admin_pin: &str) -> Result<(), CommandError> { + let admin_pin_string = get_cstring(admin_pin)?; + unsafe { get_command_result(nitrokey_sys::NK_build_aes_key(admin_pin_string.as_ptr())) } + } } /// Connects to a Nitrokey device. This method can be used to connect to any connected device, @@ -532,9 +600,9 @@ pub fn connect() -> Result<DeviceWrapper, CommandError> { match nitrokey_sys::NK_login_auto() { 1 => match get_connected_device() { Some(wrapper) => Ok(wrapper), - None => Err(CommandError::Unknown), + None => Err(CommandError::Undefined), }, - _ => Err(CommandError::Unknown), + _ => Err(CommandError::Undefined), } } } @@ -623,7 +691,7 @@ impl Pro { // TODO: maybe Option instead of Result? match connect_model(Model::Pro) { true => Ok(Pro {}), - false => Err(CommandError::Unknown), + false => Err(CommandError::Undefined), } } } @@ -663,7 +731,84 @@ impl Storage { // TODO: maybe Option instead of Result? match connect_model(Model::Storage) { true => Ok(Storage {}), - false => Err(CommandError::Unknown), + false => Err(CommandError::Undefined), + } + } + + /// Changes the update PIN. + /// + /// The update PIN is used to enable firmware updates. Unlike the user and the admin PIN, the + /// update PIN is not managed by the OpenPGP smart card but by the Nitrokey firmware. There is + /// no retry counter as with the other PIN types. + /// + /// # Errors + /// + /// - [`InvalidString`][] if one of the provided passwords contains a null byte + /// - [`WrongPassword`][] if the current update password is wrong + /// + /// # Example + /// + /// ```no_run + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::Storage::connect()?; + /// match device.change_update_pin("12345678", "87654321") { + /// Ok(()) => println!("Updated update PIN."), + /// Err(err) => println!("Failed to update update PIN: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword + pub fn change_update_pin(&self, current: &str, new: &str) -> Result<(), CommandError> { + let current_string = get_cstring(current)?; + let new_string = get_cstring(new)?; + unsafe { + get_command_result(nitrokey_sys::NK_change_update_password( + current_string.as_ptr(), + new_string.as_ptr(), + )) + } + } + + /// Enables the firmware update mode. + /// + /// During firmware update mode, the Nitrokey can no longer be accessed using HID commands. + /// To resume normal operation, run `dfu-programmer at32uc3a3256s launch`. In order to enter + /// the firmware update mode, you need the update password that can be changed using the + /// [`change_update_pin`][] method. + /// + /// # Errors + /// + /// - [`InvalidString`][] if one of the provided passwords contains a null byte + /// - [`WrongPassword`][] if the current update password is wrong + /// + /// # Example + /// + /// ```no_run + /// # use nitrokey::CommandError; + /// + /// # fn try_main() -> Result<(), CommandError> { + /// let device = nitrokey::Storage::connect()?; + /// match device.enable_firmware_update("12345678") { + /// Ok(()) => println!("Nitrokey entered update mode."), + /// Err(err) => println!("Could not enter update mode: {}", err), + /// }; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword + pub fn enable_firmware_update(&self, update_pin: &str) -> Result<(), CommandError> { + let update_pin_string = get_cstring(update_pin)?; + unsafe { + get_command_result(nitrokey_sys::NK_enable_firmware_update( + update_pin_string.as_ptr(), + )) } } diff --git a/nitrokey/src/otp.rs b/nitrokey/src/otp.rs index 6f6bd80..9f0a388 100644 --- a/nitrokey/src/otp.rs +++ b/nitrokey/src/otp.rs @@ -151,27 +151,27 @@ pub trait ConfigureOtp { /// Provides methods to generate OTP codes and to query OTP slots on a Nitrokey /// device. pub trait GenerateOtp { - /// Sets the time on the Nitrokey. This command may set the time to arbitrary values. `time` - /// is the number of seconds since January 1st, 1970 (Unix timestamp). + /// Sets the time on the Nitrokey. + /// + /// `time` is the number of seconds since January 1st, 1970 (Unix timestamp). Unless `force` + /// is set to `true`, this command fails if the timestamp on the device is larger than the + /// given timestamp or if it is zero. /// /// The time is used for TOTP generation (see [`get_totp_code`][]). /// /// # Example /// - /// ```ignore - /// extern crate chrono; - /// - /// use chrono::Utc; - /// use nitrokey::Device; + /// ```no_run + /// use std::time; + /// use nitrokey::GenerateOtp; /// # use nitrokey::CommandError; /// /// # fn try_main() -> Result<(), CommandError> { /// let device = nitrokey::connect()?; - /// let time = Utc::now().timestamp(); - /// if time < 0 { - /// println!("Timestamps before 1970-01-01 are not supported!"); - /// } else { - /// device.set_time(time as u64); + /// let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH); + /// match time { + /// Ok(time) => device.set_time(time.as_secs(), false)?, + /// Err(_) => println!("The system time is before the Unix epoch!"), /// } /// # Ok(()) /// # } @@ -183,8 +183,13 @@ pub trait GenerateOtp { /// /// [`get_totp_code`]: #method.get_totp_code /// [`Timestamp`]: enum.CommandError.html#variant.Timestamp - fn set_time(&self, time: u64) -> Result<(), CommandError> { - unsafe { get_command_result(nitrokey_sys::NK_totp_set_time(time)) } + fn set_time(&self, time: u64, force: bool) -> Result<(), CommandError> { + let result = if force { + unsafe { nitrokey_sys::NK_totp_set_time(time) } + } else { + unsafe { nitrokey_sys::NK_totp_set_time_soft(time) } + }; + get_command_result(result) } /// Returns the name of the given HOTP slot. diff --git a/nitrokey/src/pws.rs b/nitrokey/src/pws.rs index 08ac365..ebd5fcd 100644 --- a/nitrokey/src/pws.rs +++ b/nitrokey/src/pws.rs @@ -71,9 +71,18 @@ pub trait GetPasswordSafe { /// has been used. Otherwise, other applications can access the password store without /// authentication. /// + /// If this method returns an `AesDecryptionFailed` (Nitrokey Pro) or `Unknown` (Nitrokey + /// Storage) error, the AES data object on the smart card could not be accessed. This problem + /// occurs after a factory reset using `gpg --card-edit` and can be fixed using the + /// [`Device::build_aes_key`][] command. + /// /// # Errors /// + /// - [`AesDecryptionFailed`][] if the secret for the password safe could not be decrypted + /// (Nitrokey Pro only) /// - [`InvalidString`][] if one of the provided passwords contains a null byte + /// - [`Unknown`][] if the secret for the password safe could not be decrypted (Nitrokey + /// Storage only) /// - [`WrongPassword`][] if the current user password is wrong /// /// # Example @@ -99,7 +108,10 @@ pub trait GetPasswordSafe { /// /// [`device`]: struct.PasswordSafe.html#method.device /// [`lock`]: trait.Device.html#method.lock + /// [`AesDecryptionFailed`]: enum.CommandError.html#variant.AesDecryptionFailed + /// [`Device::build_aes_key`]: trait.Device.html#method.build_aes_key /// [`InvalidString`]: enum.CommandError.html#variant.InvalidString + /// [`Unknown`]: enum.CommandError.html#variant.Unknown /// [`WrongPassword`]: enum.CommandError.html#variant.WrongPassword fn get_password_safe(&self, user_pin: &str) -> Result<PasswordSafe<'_>, CommandError>; } @@ -163,7 +175,7 @@ impl<'a> PasswordSafe<'a> { /// # Errors /// /// - [`InvalidSlot`][] if the given slot is out of range - /// - [`Unknown`][] if the slot is not programmed + /// - [`Undefined`][] if the slot is not programmed /// /// # Example /// @@ -187,7 +199,7 @@ impl<'a> PasswordSafe<'a> { /// ``` /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot - /// [`Unknown`]: enum.CommandError.html#variant.Unknown + /// [`Undefined`]: enum.CommandError.html#variant.Undefined pub fn get_slot_name(&self, slot: u8) -> Result<String, CommandError> { unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_name(slot)) } } @@ -197,7 +209,7 @@ impl<'a> PasswordSafe<'a> { /// # Errors /// /// - [`InvalidSlot`][] if the given slot is out of range - /// - [`Unknown`][] if the slot is not programmed + /// - [`Undefined`][] if the slot is not programmed /// /// # Example /// @@ -217,7 +229,7 @@ impl<'a> PasswordSafe<'a> { /// ``` /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot - /// [`Unknown`]: enum.CommandError.html#variant.Unknown + /// [`Undefined`]: enum.CommandError.html#variant.Undefined pub fn get_slot_login(&self, slot: u8) -> Result<String, CommandError> { unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_login(slot)) } } @@ -227,7 +239,7 @@ impl<'a> PasswordSafe<'a> { /// # Errors /// /// - [`InvalidSlot`][] if the given slot is out of range - /// - [`Unknown`][] if the slot is not programmed + /// - [`Undefined`][] if the slot is not programmed /// /// # Example /// @@ -247,7 +259,7 @@ impl<'a> PasswordSafe<'a> { /// ``` /// /// [`InvalidSlot`]: enum.CommandError.html#variant.InvalidSlot - /// [`Unknown`]: enum.CommandError.html#variant.Unknown + /// [`Undefined`]: enum.CommandError.html#variant.Undefined pub fn get_slot_password(&self, slot: u8) -> Result<String, CommandError> { unsafe { result_from_string(nitrokey_sys::NK_get_password_safe_slot_password(slot)) } } diff --git a/nitrokey/src/util.rs b/nitrokey/src/util.rs index a2e957e..1ecc0b7 100644 --- a/nitrokey/src/util.rs +++ b/nitrokey/src/util.rs @@ -1,3 +1,4 @@ +use std::borrow; use std::ffi::{CStr, CString}; use std::fmt; use std::os::raw::{c_char, c_int}; @@ -19,7 +20,7 @@ pub enum CommandError { /// You are not authorized for this command or provided a wrong temporary /// password. NotAuthorized, - /// An error occured when getting or setting the time. + /// An error occurred when getting or setting the time. Timestamp, /// You did not provide a name for the OTP slot. NoName, @@ -29,14 +30,14 @@ pub enum CommandError { UnknownCommand, /// AES decryption failed. AesDecryptionFailed, - /// An unknown error occured. - Unknown, + /// An unknown error occurred. + Unknown(i64), + /// An unspecified error occurred. + Undefined, /// You passed a string containing a null byte. InvalidString, /// You passed an invalid slot. InvalidSlot, - /// An error occured during random number generation. - RngError, } /// Log level for libnitrokey. @@ -68,7 +69,7 @@ pub fn owned_str_from_ptr(ptr: *const c_char) -> String { pub fn result_from_string(ptr: *const c_char) -> Result<String, CommandError> { if ptr.is_null() { - return Err(CommandError::Unknown); + return Err(CommandError::Undefined); } unsafe { let s = owned_str_from_ptr(ptr); @@ -94,42 +95,53 @@ pub fn get_last_result() -> Result<(), CommandError> { pub fn get_last_error() -> CommandError { return match get_last_result() { - Ok(()) => CommandError::Unknown, + Ok(()) => CommandError::Undefined, Err(err) => err, }; } -pub fn generate_password(length: usize) -> std::io::Result<Vec<u8>> { +pub fn generate_password(length: usize) -> Vec<u8> { let mut data = vec![0u8; length]; rand::thread_rng().fill(&mut data[..]); - return Ok(data); + return data; } pub fn get_cstring<T: Into<Vec<u8>>>(s: T) -> Result<CString, CommandError> { CString::new(s).or(Err(CommandError::InvalidString)) } -impl fmt::Display for CommandError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let msg = match *self { - CommandError::WrongCrc => "A packet with a wrong checksum has been sent or received", - CommandError::WrongSlot => "The given OTP slot does not exist", - CommandError::SlotNotProgrammed => "The given OTP slot is not programmed", - CommandError::WrongPassword => "The given password is wrong", +impl CommandError { + fn as_str(&self) -> borrow::Cow<'static, str> { + match *self { + CommandError::WrongCrc => { + "A packet with a wrong checksum has been sent or received".into() + } + CommandError::WrongSlot => "The given OTP slot does not exist".into(), + CommandError::SlotNotProgrammed => "The given OTP slot is not programmed".into(), + CommandError::WrongPassword => "The given password is wrong".into(), CommandError::NotAuthorized => { - "You are not authorized for this command or provided a wrong temporary password" + "You are not authorized for this command or provided a wrong temporary \ + password" + .into() } - CommandError::Timestamp => "An error occured when getting or setting the time", - CommandError::NoName => "You did not provide a name for the OTP slot", - CommandError::NotSupported => "This command is not supported by this device", - CommandError::UnknownCommand => "This command is unknown", - CommandError::AesDecryptionFailed => "AES decryption failed", - CommandError::Unknown => "An unknown error occured", - CommandError::InvalidString => "You passed a string containing a null byte", - CommandError::InvalidSlot => "The given slot is invalid", - CommandError::RngError => "An error occured during random number generation", - }; - write!(f, "{}", msg) + CommandError::Timestamp => "An error occurred when getting or setting the time".into(), + CommandError::NoName => "You did not provide a name for the OTP slot".into(), + CommandError::NotSupported => "This command is not supported by this device".into(), + CommandError::UnknownCommand => "This command is unknown".into(), + CommandError::AesDecryptionFailed => "AES decryption failed".into(), + CommandError::Unknown(x) => { + borrow::Cow::from(format!("An unknown error occurred ({})", x)) + } + CommandError::Undefined => "An unspecified error occurred".into(), + CommandError::InvalidString => "You passed a string containing a null byte".into(), + CommandError::InvalidSlot => "The given slot is invalid".into(), + } + } +} + +impl fmt::Display for CommandError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) } } @@ -147,7 +159,7 @@ impl From<c_int> for CommandError { 9 => CommandError::UnknownCommand, 10 => CommandError::AesDecryptionFailed, 201 => CommandError::InvalidSlot, - _ => CommandError::Unknown, + x => CommandError::Unknown(x.into()), } } } diff --git a/nitrokey/tests/device.rs b/nitrokey/tests/device.rs index 26afa62..0ad4987 100644 --- a/nitrokey/tests/device.rs +++ b/nitrokey/tests/device.rs @@ -4,11 +4,15 @@ use std::ffi::CStr; use std::process::Command; use std::{thread, time}; -use nitrokey::{Authenticate, CommandError, Config, Device, Storage}; +use nitrokey::{ + Authenticate, CommandError, Config, ConfigureOtp, Device, GenerateOtp, GetPasswordSafe, + OtpMode, OtpSlotData, Storage, +}; -use crate::util::{Target, ADMIN_PASSWORD, USER_PASSWORD}; +use crate::util::{Target, ADMIN_PASSWORD, UPDATE_PIN, USER_PASSWORD}; static ADMIN_NEW_PASSWORD: &str = "1234567890"; +static UPDATE_NEW_PIN: &str = "87654321"; static USER_NEW_PASSWORD: &str = "abcdefghij"; fn count_nitrokey_block_devices() -> usize { @@ -256,12 +260,14 @@ fn unlock_user_pin() { device.unlock_user_pin(USER_PASSWORD, USER_PASSWORD) ); + // block user PIN let wrong_password = USER_PASSWORD.to_owned() + "foo"; let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); let device = require_failed_user_login(device, USER_PASSWORD, CommandError::WrongPassword); + // unblock with current PIN assert_eq!( Err(CommandError::WrongPassword), device.unlock_user_pin(USER_PASSWORD, USER_PASSWORD) @@ -269,7 +275,113 @@ fn unlock_user_pin() { assert!(device .unlock_user_pin(ADMIN_PASSWORD, USER_PASSWORD) .is_ok()); - device.authenticate_user(USER_PASSWORD).unwrap(); + let device = device.authenticate_user(USER_PASSWORD).unwrap().device(); + + // block user PIN + let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); + let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); + let device = require_failed_user_login(device, &wrong_password, CommandError::WrongPassword); + let device = require_failed_user_login(device, USER_PASSWORD, CommandError::WrongPassword); + + // unblock with new PIN + assert_eq!( + Err(CommandError::WrongPassword), + device.unlock_user_pin(USER_PASSWORD, USER_PASSWORD) + ); + assert!(device + .unlock_user_pin(ADMIN_PASSWORD, USER_NEW_PASSWORD) + .is_ok()); + + // reset user PIN + assert!(device + .change_user_pin(USER_NEW_PASSWORD, USER_PASSWORD) + .is_ok()); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn factory_reset() { + let device = Target::connect().unwrap(); + + assert_eq!( + Ok(()), + device.change_user_pin(USER_PASSWORD, USER_NEW_PASSWORD) + ); + assert_eq!( + Ok(()), + device.change_admin_pin(ADMIN_PASSWORD, ADMIN_NEW_PASSWORD) + ); + + let admin = device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap(); + let otp_data = OtpSlotData::new(1, "test", "0123468790", OtpMode::SixDigits); + assert_eq!(Ok(()), admin.write_totp_slot(otp_data, 30)); + + let device = admin.device(); + let pws = device.get_password_safe(USER_NEW_PASSWORD).unwrap(); + assert_eq!(Ok(()), pws.write_slot(0, "test", "testlogin", "testpw")); + drop(pws); + + assert_eq!( + Err(CommandError::WrongPassword), + device.factory_reset(USER_NEW_PASSWORD) + ); + assert_eq!( + Err(CommandError::WrongPassword), + device.factory_reset(ADMIN_PASSWORD) + ); + assert_eq!(Ok(()), device.factory_reset(ADMIN_NEW_PASSWORD)); + + let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device(); + + let user = device.authenticate_user(USER_PASSWORD).unwrap(); + assert_eq!( + Err(CommandError::SlotNotProgrammed), + user.get_totp_slot_name(1) + ); + + let device = user.device(); + let pws = device.get_password_safe(USER_PASSWORD).unwrap(); + assert_ne!("test".to_string(), pws.get_slot_name(0).unwrap()); + assert_ne!("testlogin".to_string(), pws.get_slot_login(0).unwrap()); + assert_ne!("testpw".to_string(), pws.get_slot_password(0).unwrap()); + + assert_eq!(Ok(()), device.build_aes_key(ADMIN_PASSWORD)); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn build_aes_key() { + let device = Target::connect().unwrap(); + + let pws = device.get_password_safe(USER_PASSWORD).unwrap(); + assert_eq!(Ok(()), pws.write_slot(0, "test", "testlogin", "testpw")); + drop(pws); + + assert_eq!( + Err(CommandError::WrongPassword), + device.build_aes_key(USER_PASSWORD) + ); + assert_eq!(Ok(()), device.build_aes_key(ADMIN_PASSWORD)); + + let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device(); + + let pws = device.get_password_safe(USER_PASSWORD).unwrap(); + assert_ne!("test".to_string(), pws.get_slot_name(0).unwrap()); + assert_ne!("testlogin".to_string(), pws.get_slot_login(0).unwrap()); + assert_ne!("testpw".to_string(), pws.get_slot_password(0).unwrap()); +} + +#[test] +#[cfg_attr(not(feature = "test-storage"), ignore)] +fn change_update_pin() { + let device = Storage::connect().unwrap(); + + assert_eq!( + Err(CommandError::WrongPassword), + device.change_update_pin(UPDATE_NEW_PIN, UPDATE_PIN) + ); + assert_eq!(Ok(()), device.change_update_pin(UPDATE_PIN, UPDATE_NEW_PIN)); + assert_eq!(Ok(()), device.change_update_pin(UPDATE_NEW_PIN, UPDATE_PIN)); } #[test] diff --git a/nitrokey/tests/otp.rs b/nitrokey/tests/otp.rs index 8e7ae08..c7d6e68 100644 --- a/nitrokey/tests/otp.rs +++ b/nitrokey/tests/otp.rs @@ -55,6 +55,16 @@ fn check_hotp_codes(device: &GenerateOtp, offset: u8) { #[test] #[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn set_time() { + let device = Target::connect().expect("Could not connect to the Nitrokey."); + assert_eq!(Ok(()), device.set_time(1546385382, true)); + assert_eq!(Ok(()), device.set_time(1546385392, false)); + assert_eq!(Err(CommandError::Timestamp), device.set_time(1546385292, false)); + assert_eq!(Ok(()), device.set_time(1546385382, true)); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] fn hotp_no_pin() { let admin = get_admin_test_device(); let config = Config::new(None, None, None, false); @@ -152,7 +162,7 @@ fn check_totp_codes(device: &GenerateOtp, factor: u64, timestamp_size: TotpTimes continue; } - assert!(device.set_time(time).is_ok()); + assert!(device.set_time(time, true).is_ok()); let result = device.get_totp_code(1); assert!(result.is_ok()); let result_code = result.unwrap(); diff --git a/nitrokey/tests/pws.rs b/nitrokey/tests/pws.rs index 875324b..5061298 100644 --- a/nitrokey/tests/pws.rs +++ b/nitrokey/tests/pws.rs @@ -11,7 +11,7 @@ use crate::util::{Target, ADMIN_PASSWORD, USER_PASSWORD}; fn get_slot_name_direct(slot: u8) -> Result<String, CommandError> { let ptr = unsafe { nitrokey_sys::NK_get_password_safe_slot_name(slot) }; if ptr.is_null() { - return Err(CommandError::Unknown); + return Err(CommandError::Undefined); } let s = unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() }; unsafe { free(ptr as *mut c_void) }; @@ -19,7 +19,7 @@ fn get_slot_name_direct(slot: u8) -> Result<String, CommandError> { true => { let error = unsafe { nitrokey_sys::NK_get_last_command_status() } as c_int; match error { - 0 => Err(CommandError::Unknown), + 0 => Err(CommandError::Undefined), other => Err(CommandError::from(other)), } } @@ -97,9 +97,9 @@ fn get_data() { assert!(pws.erase_slot(1).is_ok()); // TODO: check error codes - assert_eq!(Err(CommandError::Unknown), pws.get_slot_name(1)); - assert_eq!(Err(CommandError::Unknown), pws.get_slot_login(1)); - assert_eq!(Err(CommandError::Unknown), pws.get_slot_password(1)); + assert_eq!(Err(CommandError::Undefined), pws.get_slot_name(1)); + assert_eq!(Err(CommandError::Undefined), pws.get_slot_login(1)); + assert_eq!(Err(CommandError::Undefined), pws.get_slot_password(1)); let name = "with å"; let login = "pär@test.com"; @@ -135,19 +135,19 @@ fn write() { ); assert!(pws.write_slot(0, "", "login", "password").is_ok()); - assert_eq!(Err(CommandError::Unknown), pws.get_slot_name(0)); + assert_eq!(Err(CommandError::Undefined), pws.get_slot_name(0)); assert_eq!(Ok(String::from("login")), pws.get_slot_login(0)); assert_eq!(Ok(String::from("password")), pws.get_slot_password(0)); assert!(pws.write_slot(0, "name", "", "password").is_ok()); assert_eq!(Ok(String::from("name")), pws.get_slot_name(0)); - assert_eq!(Err(CommandError::Unknown), pws.get_slot_login(0)); + assert_eq!(Err(CommandError::Undefined), pws.get_slot_login(0)); assert_eq!(Ok(String::from("password")), pws.get_slot_password(0)); assert!(pws.write_slot(0, "name", "login", "").is_ok()); assert_eq!(Ok(String::from("name")), pws.get_slot_name(0)); assert_eq!(Ok(String::from("login")), pws.get_slot_login(0)); - assert_eq!(Err(CommandError::Unknown), pws.get_slot_password(0)); + assert_eq!(Err(CommandError::Undefined), pws.get_slot_password(0)); } #[test] @@ -160,5 +160,5 @@ fn erase() { assert!(pws.write_slot(0, "name", "login", "password").is_ok()); assert!(pws.erase_slot(0).is_ok()); assert!(pws.erase_slot(0).is_ok()); - assert_eq!(Err(CommandError::Unknown), pws.get_slot_name(0)); + assert_eq!(Err(CommandError::Undefined), pws.get_slot_name(0)); } diff --git a/nitrokey/tests/util/mod.rs b/nitrokey/tests/util/mod.rs index c2c94e2..5e495d8 100644 --- a/nitrokey/tests/util/mod.rs +++ b/nitrokey/tests/util/mod.rs @@ -1,4 +1,5 @@ pub static ADMIN_PASSWORD: &str = "12345678"; +pub static UPDATE_PIN: &str = "12345678"; pub static USER_PASSWORD: &str = "123456"; #[cfg(not(feature = "test-storage"))] |