nitrokey 0.9.0

Bindings to libnitrokey for communication with Nitrokey devices
Documentation
// Copyright (C) 2018-2019 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

mod util;

use std::ffi::CStr;

use libc::{c_int, c_void, free};
use nitrokey::{
    CommandError, Device, Error, GetPasswordSafe, LibraryError, PasswordSafe, PasswordSlot,
    DEFAULT_ADMIN_PIN, DEFAULT_USER_PIN,
};
use nitrokey_sys;
use nitrokey_test::test as test_device;

fn get_slot_name_direct(slot: u8) -> Result<String, Error> {
    let ptr = unsafe { nitrokey_sys::NK_get_password_safe_slot_name(slot) };
    if ptr.is_null() {
        return Err(Error::UnexpectedError("null pointer".to_owned()));
    }
    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 => Ok(s),
                other => Err(Error::from(other)),
            }
        }
        false => Ok(s),
    }
}

fn get_pws<'a, T>(device: &mut T) -> PasswordSafe<'_, 'a>
where
    T: Device<'a>,
{
    unwrap_ok!(device.get_password_safe(DEFAULT_USER_PIN))
}

#[test_device]
fn enable(device: DeviceWrapper) {
    let mut device = device;
    assert_cmd_err!(
        CommandError::WrongPassword,
        device.get_password_safe(&(DEFAULT_USER_PIN.to_owned() + "123"))
    );
    assert_any_ok!(device.get_password_safe(DEFAULT_USER_PIN));
    assert_cmd_err!(
        CommandError::WrongPassword,
        device.get_password_safe(DEFAULT_ADMIN_PIN)
    );
    assert_any_ok!(device.get_password_safe(DEFAULT_USER_PIN));
}

#[test_device]
fn drop(device: DeviceWrapper) {
    let mut device = device;
    {
        let mut pws = get_pws(&mut device);
        assert_ok!((), pws.write_slot(1, "name", "login", "password"));

        let slot = unwrap_ok!(pws.get_slot_unchecked(1));
        assert_ok!("name".to_string(), slot.get_name());
        let result = get_slot_name_direct(1);
        assert_ok!(String::from("name"), result);
    }
    let result = get_slot_name_direct(1);
    assert_ok!(String::from("name"), result);
    assert_ok!((), device.lock());
    let result = get_slot_name_direct(1);
    assert_cmd_err!(CommandError::NotAuthorized, result);
}

#[test_device]
fn get_status(device: DeviceWrapper) {
    fn get_pws_status(pws: &PasswordSafe<'_, '_>) -> Vec<Option<u8>> {
        unwrap_ok!(pws.get_slots())
            .iter()
            .map(|opt| opt.as_ref().map(PasswordSlot::index))
            .collect()
    }

    let mut device = device;
    let mut pws = get_pws(&mut device);
    let slot_count = pws.get_slot_count();
    for i in 0..slot_count {
        assert_ok!((), pws.erase_slot(i));
    }
    assert_eq!(vec![None; slot_count.into()], get_pws_status(&pws));

    assert_ok!((), pws.write_slot(1, "name", "login", "password"));
    for (i, slot) in get_pws_status(&pws).into_iter().enumerate() {
        if i == 1 {
            assert_eq!(Some(1), slot);
        } else {
            assert_eq!(None, slot);
        }
    }

    for i in 0..slot_count {
        assert_ok!((), pws.write_slot(i, "name", "login", "password"));
    }
    assert_eq!(
        (0..slot_count).map(Some).collect::<Vec<_>>(),
        get_pws_status(&pws)
    );
}

#[test_device]
fn get_data(device: DeviceWrapper) {
    let mut device = device;
    let mut pws = get_pws(&mut device);
    assert_ok!((), pws.write_slot(1, "name", "login", "password"));

    let slot = unwrap_ok!(pws.get_slot_unchecked(1));
    assert_ok!("name".to_string(), slot.get_name());
    assert_ok!("login".to_string(), slot.get_login());
    assert_ok!("password".to_string(), slot.get_password());
    drop(slot);

    assert_ok!((), pws.erase_slot(1));

    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot(1));
    let slot = unwrap_ok!(pws.get_slot_unchecked(1));
    assert_ok!(String::new(), slot.get_name());
    assert_ok!(String::new(), slot.get_login());
    assert_ok!(String::new(), slot.get_password());
    drop(slot);

    let name = "with å";
    let login = "pär@test.com";
    let password = "'i3lJc[09?I:,[u7dWz9";
    assert_ok!((), pws.write_slot(1, name, login, password));

    let slot = unwrap_ok!(pws.get_slot_unchecked(1));
    assert_ok!(name.to_string(), slot.get_name());
    assert_ok!(login.to_string(), slot.get_login());
    assert_ok!(password.to_string(), slot.get_password());
    drop(slot);

    let slot_count = pws.get_slot_count();
    assert_lib_err!(LibraryError::InvalidSlot, pws.get_slot(slot_count));
    assert_lib_err!(
        LibraryError::InvalidSlot,
        pws.get_slot_unchecked(slot_count)
    );
}

#[test_device]
fn write(device: DeviceWrapper) {
    let mut device = device;
    let mut pws = get_pws(&mut device);

    assert_lib_err!(
        LibraryError::InvalidSlot,
        pws.write_slot(pws.get_slot_count(), "name", "login", "password")
    );

    assert_ok!((), pws.write_slot(0, "", "login", "password"));

    let slot = unwrap_ok!(pws.get_slot_unchecked(0));
    assert_ok!(String::new(), slot.get_name());
    assert_ok!(String::from("login"), slot.get_login());
    assert_ok!(String::from("password"), slot.get_password());

    assert_ok!((), pws.write_slot(0, "name", "", "password"));

    let slot = unwrap_ok!(pws.get_slot_unchecked(0));
    assert_ok!(String::from("name"), slot.get_name());
    assert_ok!(String::new(), slot.get_login());
    assert_ok!(String::from("password"), slot.get_password());

    assert_ok!((), pws.write_slot(0, "name", "login", ""));

    let slot = unwrap_ok!(pws.get_slot_unchecked(0));
    assert_ok!(String::from("name"), slot.get_name());
    assert_ok!(String::from("login"), slot.get_login());
    assert_ok!(String::new(), slot.get_password());
}

#[test_device]
fn erase(device: DeviceWrapper) {
    let mut device = device;
    let mut pws = get_pws(&mut device);
    assert_lib_err!(
        LibraryError::InvalidSlot,
        pws.erase_slot(pws.get_slot_count())
    );

    assert_ok!((), pws.write_slot(0, "name", "login", "password"));
    assert_ok!((), pws.erase_slot(0));
    assert_ok!((), pws.erase_slot(0));
    assert_cmd_err!(CommandError::SlotNotProgrammed, pws.get_slot(0));
}