aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml1
-rw-r--r--TODO.md3
-rw-r--r--src/auth.rs16
-rw-r--r--src/device.rs68
-rw-r--r--src/lib.rs5
-rw-r--r--src/tests/device.rs51
-rw-r--r--src/tests/otp.rs29
-rw-r--r--src/tests/util.rs9
-rw-r--r--src/util.rs7
9 files changed, 143 insertions, 46 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 09f44ba..524733d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,6 +15,7 @@ license = "MIT"
default = ["test-no-device"]
test-no-device = []
test-pro = []
+test-storage = []
[dependencies]
libc = "0.2"
diff --git a/TODO.md b/TODO.md
index d57260c..9b9bc72 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,4 +1,3 @@
-- Add support and tests for the Nitrokey Storage.
- Add support for the currently unsupported commands:
- `NK_set_unencrypted_volume_rorw_pin_type_user`
- `NK_lock_device`
@@ -41,3 +40,5 @@
- Fix segmentation faults when freeing string literals with old Nitrokey
versions (fixed in libnitrokey commit 7a8550d).
- Prevent construction of internal types.
+- Add Storage-only examples to the `DeviceWrapper` documentation.
+- Fix generic connection (`get_connected_device`).
diff --git a/src/auth.rs b/src/auth.rs
index 80abbc0..c772faf 100644
--- a/src/auth.rs
+++ b/src/auth.rs
@@ -1,5 +1,5 @@
use config::{Config, RawConfig};
-use device::{Device, DeviceWrapper, Pro};
+use device::{Device, DeviceWrapper, Pro, Storage};
use nitrokey_sys;
use std::ffi::CString;
use std::ops::Deref;
@@ -387,3 +387,17 @@ impl Authenticate for Pro {
})
}
}
+
+impl Authenticate for Storage {
+ fn authenticate_user(self, password: &str) -> Result<User<Self>, (Self, CommandError)> {
+ authenticate(self, password, |password_ptr, temp_password_ptr| unsafe {
+ nitrokey_sys::NK_user_authenticate(password_ptr, temp_password_ptr)
+ })
+ }
+
+ fn authenticate_admin(self, password: &str) -> Result<Admin<Self>, (Self, CommandError)> {
+ authenticate(self, password, |password_ptr, temp_password_ptr| unsafe {
+ nitrokey_sys::NK_first_authenticate(password_ptr, temp_password_ptr)
+ })
+ }
+}
diff --git a/src/device.rs b/src/device.rs
index 684f64a..1201540 100644
--- a/src/device.rs
+++ b/src/device.rs
@@ -54,7 +54,7 @@ enum Model {
#[derive(Debug)]
pub enum DeviceWrapper {
/// A Nitrokey Storage device.
- Storage(()),
+ Storage(Storage),
/// A Nitrokey Pro device.
Pro(Pro),
}
@@ -101,6 +101,48 @@ pub enum DeviceWrapper {
#[derive(Debug)]
pub struct Pro {}
+/// A Nitrokey Storage device without user or admin authentication.
+///
+/// Use the global function [`connect`][] to obtain an instance wrapper or the method
+/// [`connect`][`Storage::connect`] to directly obtain an instance. If you want to execute a
+/// command that requires user or admin authentication, use [`authenticate_admin`][] or
+/// [`authenticate_user`][].
+///
+/// # Examples
+///
+/// Authentication with error handling:
+///
+/// ```no_run
+/// use nitrokey::{Authenticate, User, Storage};
+/// # use nitrokey::CommandError;
+///
+/// fn perform_user_task(device: &User<Storage>) {}
+/// fn perform_other_task(device: &Storage) {}
+///
+/// # fn try_main() -> Result<(), CommandError> {
+/// let device = nitrokey::Storage::connect()?;
+/// let device = match device.authenticate_user("123456") {
+/// Ok(user) => {
+/// perform_user_task(&user);
+/// user.device()
+/// },
+/// Err((device, err)) => {
+/// println!("Could not authenticate as user: {:?}", err);
+/// device
+/// },
+/// };
+/// perform_other_task(&device);
+/// # Ok(())
+/// # }
+/// ```
+///
+/// [`authenticate_admin`]: trait.Authenticate.html#method.authenticate_admin
+/// [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user
+/// [`connect`]: fn.connect.html
+/// [`Storage::connect`]: #method.connect
+#[derive(Debug)]
+pub struct Storage {}
+
/// A Nitrokey device.
///
/// This trait provides the commands that can be executed without authentication and that are
@@ -368,7 +410,7 @@ fn connect_model(model: Model) -> bool {
impl DeviceWrapper {
fn device(&self) -> &Device {
match *self {
- DeviceWrapper::Storage(_) => panic!("..."),
+ DeviceWrapper::Storage(ref storage) => storage,
DeviceWrapper::Pro(ref pro) => pro,
}
}
@@ -415,3 +457,25 @@ impl Drop for Pro {
impl Device for Pro {}
impl GenerateOtp for Pro {}
+
+impl Storage {
+ pub fn connect() -> Result<Storage, CommandError> {
+ // TODO: maybe Option instead of Result?
+ match connect_model(Model::Storage) {
+ true => Ok(Storage {}),
+ false => Err(CommandError::Unknown),
+ }
+ }
+}
+
+impl Drop for Storage {
+ fn drop(&mut self) {
+ unsafe {
+ nitrokey_sys::NK_logout();
+ }
+ }
+}
+
+impl Device for Storage {}
+
+impl GenerateOtp for Storage {}
diff --git a/src/lib.rs b/src/lib.rs
index 9dad9f9..4d5452d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -8,7 +8,7 @@
//!
//! Use [`connect`][] to connect to any Nitrokey device. The method will return a
//! [`DeviceWrapper`][] that abstracts over the supported Nitrokey devices. You can also use
-//! [`Pro::connect`][] to connect to a specific device.
+//! [`Pro::connect`][] or [`Storage::connect`][] to connect to a specific device.
//!
//! You can then use [`authenticate_user`][] or [`authenticate_admin`][] to get an authenticated
//! device that can perform operations that require authentication. You can use [`device`][] to go
@@ -76,6 +76,7 @@
//! [`authenticate_user`]: trait.Authenticate.html#method.authenticate_user
//! [`connect`]: fn.connect.html
//! [`Pro::connect`]: struct.Pro.html#fn.connect.html
+//! [`Storage::connect`]: struct.Storage.html#fn.connect.html
//! [`device`]: struct.User.html#method.device
//! [`get_hotp_code`]: trait.GenerateOtp.html#method.get_hotp_code
//! [`get_totp_code`]: trait.GenerateOtp.html#method.get_totp_code
@@ -96,7 +97,7 @@ mod util;
mod tests;
pub use config::Config;
-pub use device::{connect, Device, DeviceWrapper, Pro};
+pub use device::{connect, Device, DeviceWrapper, Pro, Storage};
pub use auth::{Admin, Authenticate, User};
pub use otp::{ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData};
pub use util::{CommandError, CommandStatus, LogLevel};
diff --git a/src/tests/device.rs b/src/tests/device.rs
index 394861c..80541d4 100644
--- a/src/tests/device.rs
+++ b/src/tests/device.rs
@@ -1,6 +1,6 @@
use std::ffi::CStr;
-use {Authenticate, CommandError, CommandStatus, Config, Device, Pro};
-use tests::util::{ADMIN_PASSWORD, USER_PASSWORD};
+use {Authenticate, CommandError, CommandStatus, Config, Device};
+use tests::util::{Target, ADMIN_PASSWORD, USER_PASSWORD};
static ADMIN_NEW_PASSWORD: &str = "1234567890";
static USER_NEW_PASSWORD: &str = "abcdefghij";
@@ -10,17 +10,23 @@ static USER_NEW_PASSWORD: &str = "abcdefghij";
fn connect_no_device() {
assert!(::connect().is_err());
assert!(::Pro::connect().is_err());
- // TODO: test storage
- // assert!(::Storage::connect().is_err());
+ assert!(::Storage::connect().is_err());
}
#[test]
#[cfg_attr(not(feature = "test-pro"), ignore)]
fn connect_pro() {
assert!(::connect().is_ok());
- assert!(Pro::connect().is_ok());
- // TODO: test storage
- // assert!(::Storage::connect().is_err());
+ assert!(::Pro::connect().is_ok());
+ assert!(::Storage::connect().is_err());
+}
+
+#[test]
+#[cfg_attr(not(feature = "test-storage"), ignore)]
+fn connect_storage() {
+ assert!(::connect().is_ok());
+ assert!(::Pro::connect().is_err());
+ assert!(::Storage::connect().is_ok());
}
fn assert_empty_serial_number() {
@@ -33,16 +39,16 @@ fn assert_empty_serial_number() {
}
#[test]
-#[cfg_attr(not(feature = "test-pro"), ignore)]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
fn disconnect() {
- ::connect().unwrap();
+ Target::connect().unwrap();
assert_empty_serial_number();
- ::connect()
+ Target::connect()
.unwrap()
.authenticate_admin(ADMIN_PASSWORD)
.unwrap();
assert_empty_serial_number();
- ::connect()
+ Target::connect()
.unwrap()
.authenticate_user(USER_PASSWORD)
.unwrap();
@@ -50,9 +56,9 @@ fn disconnect() {
}
#[test]
-#[cfg_attr(not(feature = "test-pro"), ignore)]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
fn get_serial_number() {
- let device = ::connect().unwrap();
+ let device = Target::connect().unwrap();
let result = device.get_serial_number();
assert!(result.is_ok());
let serial_number = result.unwrap();
@@ -61,8 +67,9 @@ fn get_serial_number() {
}
#[test]
#[cfg_attr(not(feature = "test-pro"), ignore)]
+// TODO: adapt for storage
fn get_firmware_version() {
- let device = ::connect().unwrap();
+ let device = Target::connect().unwrap();
assert_eq!(0, device.get_major_firmware_version());
let minor = device.get_minor_firmware_version();
assert!(minor == 7 || minor == 8);
@@ -89,9 +96,9 @@ fn user_retry<T: Authenticate + Device>(device: T, suffix: &str, count: u8) -> T
}
#[test]
-#[cfg_attr(not(feature = "test-pro"), ignore)]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
fn get_retry_count() {
- let device = ::connect().unwrap();
+ let device = Target::connect().unwrap();
let device = admin_retry(device, "", 3);
let device = admin_retry(device, "123", 2);
@@ -105,9 +112,9 @@ fn get_retry_count() {
}
#[test]
-#[cfg_attr(not(feature = "test-pro"), ignore)]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
fn config() {
- let device = ::connect().unwrap();
+ let device = Target::connect().unwrap();
let admin = device.authenticate_admin(ADMIN_PASSWORD).unwrap();
let config = Config::new(None, None, None, true);
assert_eq!(CommandStatus::Success, admin.write_config(config));
@@ -132,9 +139,9 @@ fn config() {
}
#[test]
-#[cfg_attr(not(feature = "test-pro"), ignore)]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
fn change_user_pin() {
- let device = ::connect().unwrap();
+ let device = Target::connect().unwrap();
let device = device.authenticate_user(USER_PASSWORD).unwrap().device();
let device = device.authenticate_user(USER_NEW_PASSWORD).unwrap_err().0;
@@ -158,9 +165,9 @@ fn change_user_pin() {
}
#[test]
-#[cfg_attr(not(feature = "test-pro"), ignore)]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
fn change_admin_pin() {
- let device = ::connect().unwrap();
+ let device = Target::connect().unwrap();
let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device();
let device = device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err().0;
diff --git a/src/tests/otp.rs b/src/tests/otp.rs
index e96bbfe..10f569d 100644
--- a/src/tests/otp.rs
+++ b/src/tests/otp.rs
@@ -1,7 +1,7 @@
use std::ops::Deref;
-use {Admin, Authenticate, CommandError, CommandStatus, Config, ConfigureOtp, DeviceWrapper, GenerateOtp,
+use {Admin, Authenticate, CommandError, CommandStatus, Config, ConfigureOtp, GenerateOtp,
OtpMode, OtpSlotData};
-use tests::util::{ADMIN_PASSWORD, USER_PASSWORD};
+use tests::util::{Target, ADMIN_PASSWORD, USER_PASSWORD};
// test suite according to RFC 4226, Appendix D
static HOTP_SECRET: &str = "3132333435363738393031323334353637383930";
@@ -21,8 +21,8 @@ static TOTP_CODES: &[(u64, &str)] = &[
(20000000000, "65353130"),
];
-fn get_admin_test_device() -> Admin<DeviceWrapper> {
- ::connect().expect("Could not connect to the Nitrokey Pro.")
+fn get_admin_test_device() -> Admin<Target> {
+ Target::connect().expect("Could not connect to the Nitrokey Pro.")
.authenticate_admin(ADMIN_PASSWORD)
.expect("Could not login as admin.")
}
@@ -40,7 +40,7 @@ fn check_hotp_codes(device: &GenerateOtp) {
}
#[test]
-#[cfg_attr(not(feature = "test-pro"), ignore)]
+#[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);
@@ -54,7 +54,7 @@ fn hotp_no_pin() {
}
#[test]
-#[cfg_attr(not(feature = "test-pro"), ignore)]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
fn hotp_pin() {
let admin = get_admin_test_device();
let config = Config::new(None, None, None, true);
@@ -68,7 +68,7 @@ fn hotp_pin() {
}
#[test]
-#[cfg_attr(not(feature = "test-pro"), ignore)]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
fn hotp_slot_name() {
let admin = get_admin_test_device();
let slot_data = OtpSlotData::new(1, "test-hotp", HOTP_SECRET, OtpMode::SixDigits);
@@ -82,7 +82,7 @@ fn hotp_slot_name() {
}
#[test]
-#[cfg_attr(not(feature = "test-pro"), ignore)]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
fn hotp_error() {
let admin = get_admin_test_device();
let slot_data = OtpSlotData::new(1, "", HOTP_SECRET, OtpMode::SixDigits);
@@ -100,7 +100,7 @@ fn hotp_error() {
}
#[test]
-#[cfg_attr(not(feature = "test-pro"), ignore)]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
fn hotp_erase() {
let admin = get_admin_test_device();
let config = Config::new(None, None, None, false);
@@ -141,7 +141,7 @@ fn check_totp_codes(device: &GenerateOtp) {
}
#[test]
-#[cfg_attr(not(feature = "test-pro"), ignore)]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
fn totp_no_pin() {
// TODO: this test may fail due to bad timing --> find solution
let admin = get_admin_test_device();
@@ -156,7 +156,7 @@ fn totp_no_pin() {
}
#[test]
-#[cfg_attr(not(feature = "test-pro"), ignore)]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
fn totp_pin() {
// TODO: this test may fail due to bad timing --> find solution
let admin = get_admin_test_device();
@@ -171,7 +171,7 @@ fn totp_pin() {
}
#[test]
-#[cfg_attr(not(feature = "test-pro"), ignore)]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
fn totp_slot_name() {
let admin = get_admin_test_device();
let slot_data = OtpSlotData::new(1, "test-totp", TOTP_SECRET, OtpMode::EightDigits);
@@ -186,7 +186,7 @@ fn totp_slot_name() {
}
#[test]
-#[cfg_attr(not(feature = "test-pro"), ignore)]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
fn totp_error() {
let admin = get_admin_test_device();
let slot_data = OtpSlotData::new(1, "", HOTP_SECRET, OtpMode::SixDigits);
@@ -204,7 +204,7 @@ fn totp_error() {
}
#[test]
-#[cfg_attr(not(feature = "test-pro"), ignore)]
+#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)]
fn totp_erase() {
let admin = get_admin_test_device();
let config = Config::new(None, None, None, false);
@@ -224,4 +224,3 @@ fn totp_erase() {
assert_eq!("test2", device.get_totp_slot_name(2).unwrap());
}
-
diff --git a/src/tests/util.rs b/src/tests/util.rs
index cbf6b93..c6fbb8f 100644
--- a/src/tests/util.rs
+++ b/src/tests/util.rs
@@ -1,2 +1,11 @@
pub static ADMIN_PASSWORD: &str = "12345678";
pub static USER_PASSWORD: &str = "123456";
+
+#[cfg(feature = "test-no-device")]
+pub type Target = ::Pro;
+
+#[cfg(feature = "test-pro")]
+pub type Target = ::Pro;
+
+#[cfg(feature = "test-storage")]
+pub type Target = ::Storage;
diff --git a/src/util.rs b/src/util.rs
index 22cccb3..364b0de 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -48,9 +48,10 @@ pub enum CommandStatus {
Error(CommandError),
}
-/// Log level for libnitrokey. Setting the log level to a lower level enables all output from
-/// higher levels too. Currently, only the log levels `Warning`, `DebugL1`, `Debug` and `DebugL2`
-/// are actually used.
+/// Log level for libnitrokey.
+///
+/// Setting the log level to a lower level enables all output from higher levels too. Currently,
+/// only the log levels `Warning`, `DebugL1`, `Debug` and `DebugL2` are actually used.
#[derive(Debug, PartialEq)]
pub enum LogLevel {
/// Error messages. Currently not used.