aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Mueller <deso@posteo.net>2019-01-05 16:04:49 -0800
committerDaniel Mueller <deso@posteo.net>2019-01-05 16:04:49 -0800
commitd9adac05dfa5de83465fde3479df4fd898ebd8bd (patch)
tree3bcb5585b4f821250276d6bf1eadd05f1748d016
parentbbb54f26c6101225a4f79f2f7f89cf5d71a62dd1 (diff)
downloadnitrocli-d9adac05dfa5de83465fde3479df4fd898ebd8bd.tar.gz
nitrocli-d9adac05dfa5de83465fde3479df4fd898ebd8bd.tar.bz2
Update nitrokey crate to 0.3.0
This change updates the nitrokey crate to version 0.3.0. Import subrepo nitrokey/:nitrokey at 3593df8844b80741e2d33c8e5af80e65760dc058
-rw-r--r--nitrocli/CHANGELOG.md1
-rw-r--r--nitrocli/Cargo.lock4
-rw-r--r--nitrocli/Cargo.toml2
-rw-r--r--nitrocli/src/commands.rs11
-rw-r--r--nitrokey/CHANGELOG.md13
-rw-r--r--nitrokey/Cargo.toml4
-rw-r--r--nitrokey/TODO.md5
-rw-r--r--nitrokey/src/auth.rs5
-rw-r--r--nitrokey/src/device.rs153
-rw-r--r--nitrokey/src/otp.rs33
-rw-r--r--nitrokey/src/pws.rs24
-rw-r--r--nitrokey/src/util.rs70
-rw-r--r--nitrokey/tests/device.rs118
-rw-r--r--nitrokey/tests/otp.rs12
-rw-r--r--nitrokey/tests/pws.rs18
-rw-r--r--nitrokey/tests/util/mod.rs1
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"))]