diff options
Diffstat (limited to 'nitrokey/tests')
| -rw-r--r-- | nitrokey/tests/device.rs | 313 | ||||
| -rw-r--r-- | nitrokey/tests/otp.rs | 289 | ||||
| -rw-r--r-- | nitrokey/tests/pws.rs | 164 | ||||
| -rw-r--r-- | nitrokey/tests/util/mod.rs | 8 | 
4 files changed, 774 insertions, 0 deletions
| diff --git a/nitrokey/tests/device.rs b/nitrokey/tests/device.rs new file mode 100644 index 0000000..26afa62 --- /dev/null +++ b/nitrokey/tests/device.rs @@ -0,0 +1,313 @@ +mod util; + +use std::ffi::CStr; +use std::process::Command; +use std::{thread, time}; + +use nitrokey::{Authenticate, CommandError, Config, Device, Storage}; + +use crate::util::{Target, ADMIN_PASSWORD, USER_PASSWORD}; + +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.replace("_", " ") == "Nitrokey Storage") +        .count() +} + +#[test] +#[cfg_attr(any(feature = "test-pro", feature = "test-storage"), ignore)] +fn connect_no_device() { +    assert!(nitrokey::connect().is_err()); +    assert!(nitrokey::Pro::connect().is_err()); +    assert!(nitrokey::Storage::connect().is_err()); +} + +#[test] +#[cfg_attr(not(feature = "test-pro"), ignore)] +fn connect_pro() { +    assert!(nitrokey::connect().is_ok()); +    assert!(nitrokey::Pro::connect().is_ok()); +    assert!(nitrokey::Storage::connect().is_err()); +    match nitrokey::connect().unwrap() { +        nitrokey::DeviceWrapper::Pro(_) => assert!(true), +        nitrokey::DeviceWrapper::Storage(_) => assert!(false), +    }; +} + +#[test] +#[cfg_attr(not(feature = "test-storage"), ignore)] +fn connect_storage() { +    assert!(nitrokey::connect().is_ok()); +    assert!(nitrokey::Pro::connect().is_err()); +    assert!(nitrokey::Storage::connect().is_ok()); +    match nitrokey::connect().unwrap() { +        nitrokey::DeviceWrapper::Pro(_) => assert!(false), +        nitrokey::DeviceWrapper::Storage(_) => assert!(true), +    }; +} + +fn assert_empty_serial_number() { +    unsafe { +        let ptr = nitrokey_sys::NK_device_serial_number(); +        assert!(!ptr.is_null()); +        let cstr = CStr::from_ptr(ptr); +        assert_eq!(cstr.to_string_lossy(), ""); +    } +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn disconnect() { +    Target::connect().unwrap(); +    assert_empty_serial_number(); +    Target::connect() +        .unwrap() +        .authenticate_admin(ADMIN_PASSWORD) +        .unwrap(); +    assert_empty_serial_number(); +    Target::connect() +        .unwrap() +        .authenticate_user(USER_PASSWORD) +        .unwrap(); +    assert_empty_serial_number(); +} + +fn require_model(model: nitrokey::Model) { +    assert_eq!(model, nitrokey::connect().unwrap().get_model()); +    assert_eq!(model, Target::connect().unwrap().get_model()); +} + +#[test] +#[cfg_attr(not(feature = "test-pro"), ignore)] +fn get_model_pro() { +    require_model(nitrokey::Model::Pro); +} + +#[test] +#[cfg_attr(not(feature = "test-storage"), ignore)] +fn get_model_storage() { +    require_model(nitrokey::Model::Storage); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn get_serial_number() { +    let device = Target::connect().unwrap(); +    let result = device.get_serial_number(); +    assert!(result.is_ok()); +    let serial_number = result.unwrap(); +    assert!(serial_number.is_ascii()); +    assert!(serial_number.chars().all(|c| c.is_ascii_hexdigit())); +} +#[test] +#[cfg_attr(not(feature = "test-pro"), ignore)] +fn get_firmware_version() { +    let device = Target::connect().unwrap(); +    assert_eq!(0, device.get_major_firmware_version()); +    let minor = device.get_minor_firmware_version(); +    assert!(minor > 0); +} + +fn admin_retry<T: Authenticate + Device>(device: T, suffix: &str, count: u8) -> T { +    let result = device.authenticate_admin(&(ADMIN_PASSWORD.to_owned() + suffix)); +    let device = match result { +        Ok(admin) => admin.device(), +        Err((device, _)) => device, +    }; +    assert_eq!(count, device.get_admin_retry_count()); +    return device; +} + +fn user_retry<T: Authenticate + Device>(device: T, suffix: &str, count: u8) -> T { +    let result = device.authenticate_user(&(USER_PASSWORD.to_owned() + suffix)); +    let device = match result { +        Ok(admin) => admin.device(), +        Err((device, _)) => device, +    }; +    assert_eq!(count, device.get_user_retry_count()); +    return device; +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn get_retry_count() { +    let device = Target::connect().unwrap(); + +    let device = admin_retry(device, "", 3); +    let device = admin_retry(device, "123", 2); +    let device = admin_retry(device, "456", 1); +    let device = admin_retry(device, "", 3); + +    let device = user_retry(device, "", 3); +    let device = user_retry(device, "123", 2); +    let device = user_retry(device, "456", 1); +    user_retry(device, "", 3); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn config() { +    let device = Target::connect().unwrap(); +    let admin = device.authenticate_admin(ADMIN_PASSWORD).unwrap(); +    let config = Config::new(None, None, None, true); +    assert!(admin.write_config(config).is_ok()); +    let get_config = admin.get_config().unwrap(); +    assert_eq!(config, get_config); + +    let config = Config::new(None, Some(9), None, true); +    assert_eq!(Err(CommandError::InvalidSlot), admin.write_config(config)); + +    let config = Config::new(Some(1), None, Some(0), false); +    assert!(admin.write_config(config).is_ok()); +    let get_config = admin.get_config().unwrap(); +    assert_eq!(config, get_config); + +    let config = Config::new(None, None, None, false); +    assert!(admin.write_config(config).is_ok()); +    let get_config = admin.get_config().unwrap(); +    assert_eq!(config, get_config); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn change_user_pin() { +    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; + +    assert!(device +        .change_user_pin(USER_PASSWORD, USER_NEW_PASSWORD) +        .is_ok()); + +    let device = device.authenticate_user(USER_PASSWORD).unwrap_err().0; +    let device = device +        .authenticate_user(USER_NEW_PASSWORD) +        .unwrap() +        .device(); + +    let result = device.change_user_pin(USER_PASSWORD, USER_PASSWORD); +    assert_eq!(Err(CommandError::WrongPassword), result); + +    assert!(device +        .change_user_pin(USER_NEW_PASSWORD, USER_PASSWORD) +        .is_ok()); + +    let device = device.authenticate_user(USER_PASSWORD).unwrap().device(); +    assert!(device.authenticate_user(USER_NEW_PASSWORD).is_err()); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn change_admin_pin() { +    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; + +    assert!(device +        .change_admin_pin(ADMIN_PASSWORD, ADMIN_NEW_PASSWORD) +        .is_ok()); + +    let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap_err().0; +    let device = device +        .authenticate_admin(ADMIN_NEW_PASSWORD) +        .unwrap() +        .device(); + +    assert_eq!( +        Err(CommandError::WrongPassword), +        device.change_admin_pin(ADMIN_PASSWORD, ADMIN_PASSWORD) +    ); + +    assert!(device +        .change_admin_pin(ADMIN_NEW_PASSWORD, ADMIN_PASSWORD) +        .is_ok()); + +    let device = device.authenticate_admin(ADMIN_PASSWORD).unwrap().device(); +    device.authenticate_admin(ADMIN_NEW_PASSWORD).unwrap_err(); +} + +fn require_failed_user_login(device: Target, password: &str, error: CommandError) -> Target { +    let result = device.authenticate_user(password); +    assert!(result.is_err()); +    let err = result.unwrap_err(); +    assert_eq!(error, err.1); +    err.0 +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn unlock_user_pin() { +    let device = Target::connect().unwrap(); +    let device = device.authenticate_user(USER_PASSWORD).unwrap().device(); +    assert!(device +        .unlock_user_pin(ADMIN_PASSWORD, USER_PASSWORD) +        .is_ok()); +    assert_eq!( +        Err(CommandError::WrongPassword), +        device.unlock_user_pin(USER_PASSWORD, USER_PASSWORD) +    ); + +    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); + +    assert_eq!( +        Err(CommandError::WrongPassword), +        device.unlock_user_pin(USER_PASSWORD, USER_PASSWORD) +    ); +    assert!(device +        .unlock_user_pin(ADMIN_PASSWORD, USER_PASSWORD) +        .is_ok()); +    device.authenticate_user(USER_PASSWORD).unwrap(); +} + +#[test] +#[cfg_attr(not(feature = "test-storage"), ignore)] +fn encrypted_volume() { +    let device = Storage::connect().unwrap(); +    assert!(device.lock().is_ok()); + +    assert_eq!(1, count_nitrokey_block_devices()); +    assert!(device.disable_encrypted_volume().is_ok()); +    assert_eq!(1, count_nitrokey_block_devices()); +    assert_eq!( +        Err(CommandError::WrongPassword), +        device.enable_encrypted_volume("123") +    ); +    assert_eq!(1, count_nitrokey_block_devices()); +    assert!(device.enable_encrypted_volume(USER_PASSWORD).is_ok()); +    assert_eq!(2, count_nitrokey_block_devices()); +    assert!(device.disable_encrypted_volume().is_ok()); +    assert_eq!(1, count_nitrokey_block_devices()); +} + +#[test] +#[cfg_attr(not(feature = "test-storage"), ignore)] +fn lock() { +    let device = Storage::connect().unwrap(); + +    assert!(device.enable_encrypted_volume(USER_PASSWORD).is_ok()); +    assert!(device.lock().is_ok()); +    assert_eq!(1, count_nitrokey_block_devices()); +} + +#[test] +#[cfg_attr(not(feature = "test-storage"), ignore)] +fn get_storage_status() { +    let device = Storage::connect().unwrap(); +    let status = device.get_status().unwrap(); + +    assert!(status.serial_number_sd_card > 0); +    assert!(status.serial_number_smart_card > 0); +} diff --git a/nitrokey/tests/otp.rs b/nitrokey/tests/otp.rs new file mode 100644 index 0000000..8e7ae08 --- /dev/null +++ b/nitrokey/tests/otp.rs @@ -0,0 +1,289 @@ +mod util; + +use std::ops::Deref; + +use nitrokey::{ +    Admin, Authenticate, CommandError, Config, ConfigureOtp, GenerateOtp, OtpMode, OtpSlotData, +}; + +use crate::util::{Target, ADMIN_PASSWORD, USER_PASSWORD}; + +// test suite according to RFC 4226, Appendix D +static HOTP_SECRET: &str = "3132333435363738393031323334353637383930"; +static HOTP_CODES: &[&str] = &[ +    "755224", "287082", "359152", "969429", "338314", "254676", "287922", "162583", "399871", +    "520489", +]; + +// test suite according to RFC 6238, Appendix B +static TOTP_SECRET: &str = "3132333435363738393031323334353637383930"; +static TOTP_CODES: &[(u64, &str)] = &[ +    (59, "94287082"), +    (1111111109, "07081804"), +    (1111111111, "14050471"), +    (1234567890, "89005924"), +    (2000000000, "69279037"), +    (20000000000, "65353130"), +]; + +#[derive(PartialEq)] +enum TotpTimestampSize { +    U32, +    U64, +} + +fn get_admin_test_device() -> Admin<Target> { +    Target::connect() +        .expect("Could not connect to the Nitrokey.") +        .authenticate_admin(ADMIN_PASSWORD) +        .expect("Could not login as admin.") +} + +fn configure_hotp(admin: &ConfigureOtp, counter: u8) { +    let slot_data = OtpSlotData::new(1, "test-hotp", HOTP_SECRET, OtpMode::SixDigits); +    assert!(admin.write_hotp_slot(slot_data, counter.into()).is_ok()); +} + +fn check_hotp_codes(device: &GenerateOtp, offset: u8) { +    HOTP_CODES.iter().enumerate().for_each(|(i, code)| { +        if i >= offset as usize { +            let result = device.get_hotp_code(1); +            assert_eq!(code, &result.unwrap()); +        } +    }); +} + +#[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); +    assert!(admin.write_config(config).is_ok()); + +    configure_hotp(&admin, 0); +    check_hotp_codes(admin.deref(), 0); + +    configure_hotp(&admin, 5); +    check_hotp_codes(admin.deref(), 5); + +    configure_hotp(&admin, 0); +    check_hotp_codes(&admin.device(), 0); +} + +#[test] +#[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); +    assert!(admin.write_config(config).is_ok()); + +    configure_hotp(&admin, 0); +    let user = admin.device().authenticate_user(USER_PASSWORD).unwrap(); +    check_hotp_codes(&user, 0); + +    assert!(user.device().get_hotp_code(1).is_err()); +} + +#[test] +#[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); +    assert!(admin.write_hotp_slot(slot_data, 0).is_ok()); + +    let device = admin.device(); +    let result = device.get_hotp_slot_name(1); +    assert_eq!("test-hotp", result.unwrap()); +    let result = device.get_hotp_slot_name(4); +    assert_eq!(CommandError::InvalidSlot, result.unwrap_err()); +} + +#[test] +#[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); +    assert_eq!( +        Err(CommandError::NoName), +        admin.write_hotp_slot(slot_data, 0) +    ); +    let slot_data = OtpSlotData::new(4, "test", HOTP_SECRET, OtpMode::SixDigits); +    assert_eq!( +        Err(CommandError::InvalidSlot), +        admin.write_hotp_slot(slot_data, 0) +    ); +    let code = admin.get_hotp_code(4); +    assert_eq!(CommandError::InvalidSlot, code.unwrap_err()); +} + +#[test] +#[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); +    assert!(admin.write_config(config).is_ok()); +    let slot_data = OtpSlotData::new(1, "test1", HOTP_SECRET, OtpMode::SixDigits); +    assert!(admin.write_hotp_slot(slot_data, 0).is_ok()); +    let slot_data = OtpSlotData::new(2, "test2", HOTP_SECRET, OtpMode::SixDigits); +    assert!(admin.write_hotp_slot(slot_data, 0).is_ok()); + +    assert!(admin.erase_hotp_slot(1).is_ok()); + +    let device = admin.device(); +    let result = device.get_hotp_slot_name(1); +    assert_eq!(CommandError::SlotNotProgrammed, result.unwrap_err()); +    let result = device.get_hotp_code(1); +    assert_eq!(CommandError::SlotNotProgrammed, result.unwrap_err()); + +    assert_eq!("test2", device.get_hotp_slot_name(2).unwrap()); +} + +fn configure_totp(admin: &ConfigureOtp, factor: u64) { +    let slot_data = OtpSlotData::new(1, "test-totp", TOTP_SECRET, OtpMode::EightDigits); +    let time_window = 30u64.checked_mul(factor).unwrap(); +    assert!(admin.write_totp_slot(slot_data, time_window as u16).is_ok()); +} + +fn check_totp_codes(device: &GenerateOtp, factor: u64, timestamp_size: TotpTimestampSize) { +    for (i, &(base_time, code)) in TOTP_CODES.iter().enumerate() { +        let time = base_time.checked_mul(factor).unwrap(); +        let is_u64 = time > u32::max_value() as u64; +        if is_u64 != (timestamp_size == TotpTimestampSize::U64) { +            continue; +        } + +        assert!(device.set_time(time).is_ok()); +        let result = device.get_totp_code(1); +        assert!(result.is_ok()); +        let result_code = result.unwrap(); +        assert_eq!( +            code, result_code, +            "TOTP code {} should be {} but is {}", +            i, code, result_code +        ); +    } +} + +#[test] +#[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(); +    let config = Config::new(None, None, None, false); +    assert!(admin.write_config(config).is_ok()); + +    configure_totp(&admin, 1); +    check_totp_codes(admin.deref(), 1, TotpTimestampSize::U32); + +    configure_totp(&admin, 2); +    check_totp_codes(admin.deref(), 2, TotpTimestampSize::U32); + +    configure_totp(&admin, 1); +    check_totp_codes(&admin.device(), 1, TotpTimestampSize::U32); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +#[cfg_attr(feature = "test-storage", should_panic(expected = "assertion failed"))] +// Nitrokey Storage does only support timestamps that fit in a 32-bit unsigned integer.  Therefore +// the last RFC test case is expected to fail. +fn totp_no_pin_64() { +    let admin = get_admin_test_device(); +    let config = Config::new(None, None, None, false); +    assert!(admin.write_config(config).is_ok()); + +    configure_totp(&admin, 1); +    check_totp_codes(admin.deref(), 1, TotpTimestampSize::U64); + +    configure_totp(&admin, 2); +    check_totp_codes(admin.deref(), 2, TotpTimestampSize::U64); + +    configure_totp(&admin, 1); +    check_totp_codes(&admin.device(), 1, TotpTimestampSize::U64); +} + +#[test] +#[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(); +    let config = Config::new(None, None, None, true); +    assert!(admin.write_config(config).is_ok()); + +    configure_totp(&admin, 1); +    let user = admin.device().authenticate_user(USER_PASSWORD).unwrap(); +    check_totp_codes(&user, 1, TotpTimestampSize::U32); + +    assert!(user.device().get_totp_code(1).is_err()); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +#[cfg_attr(feature = "test-storage", should_panic(expected = "assertion failed"))] +// See comment for totp_no_pin_64. +fn totp_pin_64() { +    let admin = get_admin_test_device(); +    let config = Config::new(None, None, None, true); +    assert!(admin.write_config(config).is_ok()); + +    configure_totp(&admin, 1); +    let user = admin.device().authenticate_user(USER_PASSWORD).unwrap(); +    check_totp_codes(&user, 1, TotpTimestampSize::U64); + +    assert!(user.device().get_totp_code(1).is_err()); +} + +#[test] +#[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); +    assert!(admin.write_totp_slot(slot_data, 0).is_ok()); + +    let device = admin.device(); +    let result = device.get_totp_slot_name(1); +    assert!(result.is_ok()); +    assert_eq!("test-totp", result.unwrap()); +    let result = device.get_totp_slot_name(16); +    assert_eq!(CommandError::InvalidSlot, result.unwrap_err()); +} + +#[test] +#[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); +    assert_eq!( +        Err(CommandError::NoName), +        admin.write_hotp_slot(slot_data, 0) +    ); +    let slot_data = OtpSlotData::new(4, "test", HOTP_SECRET, OtpMode::SixDigits); +    assert_eq!( +        Err(CommandError::InvalidSlot), +        admin.write_hotp_slot(slot_data, 0) +    ); +    let code = admin.get_hotp_code(4); +    assert_eq!(CommandError::InvalidSlot, code.unwrap_err()); +} + +#[test] +#[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); +    assert!(admin.write_config(config).is_ok()); +    let slot_data = OtpSlotData::new(1, "test1", TOTP_SECRET, OtpMode::SixDigits); +    assert!(admin.write_totp_slot(slot_data, 0).is_ok()); +    let slot_data = OtpSlotData::new(2, "test2", TOTP_SECRET, OtpMode::SixDigits); +    assert!(admin.write_totp_slot(slot_data, 0).is_ok()); + +    assert!(admin.erase_totp_slot(1).is_ok()); + +    let device = admin.device(); +    let result = device.get_totp_slot_name(1); +    assert_eq!(CommandError::SlotNotProgrammed, result.unwrap_err()); +    let result = device.get_totp_code(1); +    assert_eq!(CommandError::SlotNotProgrammed, result.unwrap_err()); + +    assert_eq!("test2", device.get_totp_slot_name(2).unwrap()); +} diff --git a/nitrokey/tests/pws.rs b/nitrokey/tests/pws.rs new file mode 100644 index 0000000..875324b --- /dev/null +++ b/nitrokey/tests/pws.rs @@ -0,0 +1,164 @@ +mod util; + +use std::ffi::CStr; + +use libc::{c_int, c_void, free}; +use nitrokey::{CommandError, Device, GetPasswordSafe, PasswordSafe, SLOT_COUNT}; +use nitrokey_sys; + +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); +    } +    let s = unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() }; +    unsafe { free(ptr as *mut c_void) }; +    match s.is_empty() { +        true => { +            let error = unsafe { nitrokey_sys::NK_get_last_command_status() } as c_int; +            match error { +                0 => Err(CommandError::Unknown), +                other => Err(CommandError::from(other)), +            } +        } +        false => Ok(s), +    } +} + +fn get_pws(device: &Target) -> PasswordSafe { +    device.get_password_safe(USER_PASSWORD).unwrap() +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn enable() { +    let device = Target::connect().unwrap(); +    assert!(device +        .get_password_safe(&(USER_PASSWORD.to_owned() + "123")) +        .is_err()); +    assert!(device.get_password_safe(USER_PASSWORD).is_ok()); +    assert!(device.get_password_safe(ADMIN_PASSWORD).is_err()); +    assert!(device.get_password_safe(USER_PASSWORD).is_ok()); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn drop() { +    let device = Target::connect().unwrap(); +    { +        let pws = get_pws(&device); +        assert!(pws.write_slot(1, "name", "login", "password").is_ok()); +        assert_eq!("name", pws.get_slot_name(1).unwrap()); +        let result = get_slot_name_direct(1); +        assert_eq!(Ok(String::from("name")), result); +    } +    let result = get_slot_name_direct(1); +    assert_eq!(Ok(String::from("name")), result); +    assert!(device.lock().is_ok()); +    let result = get_slot_name_direct(1); +    assert_eq!(Err(CommandError::NotAuthorized), result); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn get_status() { +    let device = Target::connect().unwrap(); +    let pws = get_pws(&device); +    for i in 0..SLOT_COUNT { +        assert!(pws.erase_slot(i).is_ok(), "Could not erase slot {}", i); +    } +    let status = pws.get_slot_status().unwrap(); +    assert_eq!(status, [false; SLOT_COUNT as usize]); + +    assert!(pws.write_slot(1, "name", "login", "password").is_ok()); +    let status = pws.get_slot_status().unwrap(); +    for i in 0..SLOT_COUNT { +        assert_eq!(i == 1, status[i as usize]); +    } + +    for i in 0..SLOT_COUNT { +        assert!(pws.write_slot(i, "name", "login", "password").is_ok()); +    } +    let status = pws.get_slot_status().unwrap(); +    assert_eq!(status, [true; SLOT_COUNT as usize]); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn get_data() { +    let device = Target::connect().unwrap(); +    let pws = get_pws(&device); +    assert!(pws.write_slot(1, "name", "login", "password").is_ok()); +    assert_eq!("name", pws.get_slot_name(1).unwrap()); +    assert_eq!("login", pws.get_slot_login(1).unwrap()); +    assert_eq!("password", pws.get_slot_password(1).unwrap()); + +    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)); + +    let name = "with å"; +    let login = "pär@test.com"; +    let password = "'i3lJc[09?I:,[u7dWz9"; +    assert!(pws.write_slot(1, name, login, password).is_ok()); +    assert_eq!(name, pws.get_slot_name(1).unwrap()); +    assert_eq!(login, pws.get_slot_login(1).unwrap()); +    assert_eq!(password, pws.get_slot_password(1).unwrap()); + +    assert_eq!( +        Err(CommandError::InvalidSlot), +        pws.get_slot_name(SLOT_COUNT) +    ); +    assert_eq!( +        Err(CommandError::InvalidSlot), +        pws.get_slot_login(SLOT_COUNT) +    ); +    assert_eq!( +        Err(CommandError::InvalidSlot), +        pws.get_slot_password(SLOT_COUNT) +    ); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn write() { +    let device = Target::connect().unwrap(); +    let pws = get_pws(&device); + +    assert_eq!( +        Err(CommandError::InvalidSlot), +        pws.write_slot(SLOT_COUNT, "name", "login", "password") +    ); + +    assert!(pws.write_slot(0, "", "login", "password").is_ok()); +    assert_eq!(Err(CommandError::Unknown), 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!(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)); +} + +#[test] +#[cfg_attr(not(any(feature = "test-pro", feature = "test-storage")), ignore)] +fn erase() { +    let device = Target::connect().unwrap(); +    let pws = get_pws(&device); +    assert_eq!(Err(CommandError::InvalidSlot), pws.erase_slot(SLOT_COUNT)); + +    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)); +} diff --git a/nitrokey/tests/util/mod.rs b/nitrokey/tests/util/mod.rs new file mode 100644 index 0000000..c2c94e2 --- /dev/null +++ b/nitrokey/tests/util/mod.rs @@ -0,0 +1,8 @@ +pub static ADMIN_PASSWORD: &str = "12345678"; +pub static USER_PASSWORD: &str = "123456"; + +#[cfg(not(feature = "test-storage"))] +pub type Target = nitrokey::Pro; + +#[cfg(feature = "test-storage")] +pub type Target = nitrokey::Storage; | 
