aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml12
-rw-r--r--CMakeLists.txt2
-rw-r--r--NK_C_API.cc95
-rw-r--r--NK_C_API.h84
-rw-r--r--NitrokeyManager.cc41
-rw-r--r--README.md4
-rw-r--r--device.cc66
-rw-r--r--libnitrokey/NitrokeyManager.h6
-rw-r--r--libnitrokey/device.h57
-rw-r--r--libnitrokey/misc.h25
-rw-r--r--unittest/conftest.py72
-rw-r--r--unittest/requirements.txt3
-rw-r--r--unittest/test_multiple.py29
-rw-r--r--unittest/test_multiple_devices.cc81
-rw-r--r--unittest/test_offline.py39
-rw-r--r--unittest/test_storage.py30
16 files changed, 593 insertions, 53 deletions
diff --git a/.travis.yml b/.travis.yml
index bf195df..6bf5438 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,8 +22,20 @@ matrix:
- cmake
- libhidapi-dev
- g++-5
+ - python3
+ - python3-pip
+ - python3-requests
+ - git
sources: &sources
- ubuntu-toolchain-r-test
+ script:
+ - make -j2
+ - ctest -VV
+ - mkdir install && make install DESTDIR=install
+ - pip3 install pytest --user
+ - cd ../
+ - pip3 install -r unittest/requirements.txt --user
+ - cd unittest && python3 -m pytest -sv test_offline.py
- os: linux
dist: trusty
env: COMPILER_NAME=gcc CXX=g++-6 CC=gcc-6
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f7d98a3..7ba6f9a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -131,8 +131,8 @@ execute_process(
ENDIF()
IF((NOT ${ADD_GIT_INFO}) OR (${PROJECT_VERSION_GIT_RETURN_CODE}))
SET(PROJECT_VERSION_GIT "unknown")
- MESSAGE(STATUS "Setting Git library version to: " ${PROJECT_VERSION_GIT} )
ENDIF()
+MESSAGE(STATUS "Setting Git library version to: " ${PROJECT_VERSION_GIT} )
configure_file("version.cc.in" "version.cc" @ONLY)
diff --git a/NK_C_API.cc b/NK_C_API.cc
index 7d0a10e..eae35d5 100644
--- a/NK_C_API.cc
+++ b/NK_C_API.cc
@@ -27,6 +27,7 @@
#include "libnitrokey/LibraryException.h"
#include "libnitrokey/cxx_semantics.h"
#include "libnitrokey/stick20_commands.h"
+#include "libnitrokey/device_proto.h"
#include "version.h"
#ifdef _MSC_VER
@@ -44,6 +45,7 @@ char * strndup(const char* str, size_t maxlen) {
using namespace nitrokey;
+const uint8_t NK_PWS_SLOT_COUNT = PWS_SLOT_COUNT;
static uint8_t NK_last_command_status = 0;
static const int max_string_field_length = 100;
@@ -687,6 +689,23 @@ extern "C" {
return 0;
}
+ NK_C_API int NK_get_SD_usage_data(struct NK_SD_usage_data* out) {
+ if (out == nullptr)
+ return -1;
+ auto m = NitrokeyManager::instance();
+ auto result = get_with_status([&]() {
+ return m->get_SD_usage_data();
+ }, std::make_pair<uint8_t, uint8_t>(0, 0));
+ auto error_code = std::get<0>(result);
+ if (error_code != 0)
+ return error_code;
+
+ auto data = std::get<1>(result);
+ out->write_level_min = std::get<0>(data);
+ out->write_level_max = std::get<1>(data);
+
+ return 0;
+ }
NK_C_API char* NK_get_SD_usage_data_as_string() {
auto m = NitrokeyManager::instance();
@@ -697,19 +716,19 @@ NK_C_API char* NK_get_SD_usage_data_as_string() {
NK_C_API int NK_get_progress_bar_value() {
auto m = NitrokeyManager::instance();
- return get_with_result([&]() {
+ return std::get<1>(get_with_status([&]() {
return m->get_progress_bar_value();
- });
+ }, -2));
}
- NK_C_API int NK_get_major_firmware_version() {
+ NK_C_API uint8_t NK_get_major_firmware_version() {
auto m = NitrokeyManager::instance();
return get_with_result([&]() {
return m->get_major_firmware_version();
});
}
- NK_C_API int NK_get_minor_firmware_version() {
+ NK_C_API uint8_t NK_get_minor_firmware_version() {
auto m = NitrokeyManager::instance();
return get_with_result([&]() {
return m->get_minor_firmware_version();
@@ -736,6 +755,66 @@ NK_C_API char* NK_get_SD_usage_data_as_string() {
});
}
+ bool copy_device_info(const DeviceInfo& source, NK_device_info* target) {
+ switch (source.m_deviceModel) {
+ case DeviceModel::PRO:
+ target->model = NK_PRO;
+ break;
+ case DeviceModel::STORAGE:
+ target->model = NK_STORAGE;
+ break;
+ default:
+ return false;
+ }
+
+ target->path = strndup(source.m_path.c_str(), MAXIMUM_STR_REPLY_LENGTH);
+ target->serial_number = strndup(source.m_serialNumber.c_str(), MAXIMUM_STR_REPLY_LENGTH);
+ target->next = nullptr;
+
+ return target->path && target->serial_number;
+ }
+
+ NK_C_API struct NK_device_info* NK_list_devices() {
+ auto nm = NitrokeyManager::instance();
+ return get_with_result([&]() -> NK_device_info* {
+ auto v = nm->list_devices();
+ if (v.empty())
+ return nullptr;
+
+ auto result = new NK_device_info();
+ auto ptr = result;
+ auto first = v.begin();
+ if (!copy_device_info(*first, ptr)) {
+ NK_free_device_info(result);
+ return nullptr;
+ }
+ v.erase(first);
+
+ for (auto& info : v) {
+ ptr->next = new NK_device_info();
+ ptr = ptr->next;
+
+ if (!copy_device_info(info, ptr)) {
+ NK_free_device_info(result);
+ return nullptr;
+ }
+ }
+ return result;
+ });
+ }
+
+ NK_C_API void NK_free_device_info(struct NK_device_info* device_info) {
+ if (!device_info)
+ return;
+
+ if (device_info->next)
+ NK_free_device_info(device_info->next);
+
+ free(device_info->path);
+ free(device_info->serial_number);
+ delete device_info;
+ }
+
NK_C_API int NK_connect_with_ID(const char* id) {
auto m = NitrokeyManager::instance();
return get_with_result([&]() {
@@ -743,6 +822,14 @@ NK_C_API char* NK_get_SD_usage_data_as_string() {
});
}
+ NK_C_API int NK_connect_with_path(const char* path) {
+ auto m = NitrokeyManager::instance();
+ return get_with_result([&]() {
+ return m->connect_with_path(path) ? 1 : 0;
+ });
+ }
+
+
NK_C_API int NK_wink() {
auto m = NitrokeyManager::instance();
return get_without_result([&]() {
diff --git a/NK_C_API.h b/NK_C_API.h
index c803ef1..47b2567 100644
--- a/NK_C_API.h
+++ b/NK_C_API.h
@@ -89,6 +89,11 @@
extern "C" {
#endif
+ /**
+ * The number of slots in the password safe.
+ */
+ extern const uint8_t NK_PWS_SLOT_COUNT;
+
static const int MAXIMUM_STR_REPLY_LENGTH = 8192;
/**
@@ -109,6 +114,29 @@ extern "C" {
NK_STORAGE = 2
};
+ /**
+ * The connection info for a Nitrokey device as a linked list.
+ */
+ struct NK_device_info {
+ /**
+ * The model of the Nitrokey device.
+ */
+ enum NK_device_model model;
+ /**
+ * The USB device path for NK_connect_with_path.
+ */
+ char* path;
+ /**
+ * The serial number.
+ */
+ char* serial_number;
+ /**
+ * The pointer to the next element of the linked list or null
+ * if this is the last element in the list.
+ */
+ struct NK_device_info* next;
+ };
+
/**
* Stores the status of a Storage device.
*/
@@ -180,6 +208,23 @@ extern "C" {
bool stick_initialized;
};
+ /**
+ * Data about the usage of the SD card.
+ */
+ struct NK_SD_usage_data {
+ /**
+ * The minimum write level, as a percentage of the total card
+ * size.
+ */
+ uint8_t write_level_min;
+ /**
+ * The maximum write level, as a percentage of the total card
+ * size.
+ */
+ uint8_t write_level_max;
+ };
+
+
struct NK_storage_ProductionTest{
uint8_t FirmwareVersion_au8[2];
uint8_t FirmwareVersionInternal_u8;
@@ -580,13 +625,13 @@ extern "C" {
* Get device's major firmware version
* @return major part of the version number (e.g. 0 from 0.48, 0 from 0.7 etc.)
*/
- NK_C_API int NK_get_major_firmware_version();
+ NK_C_API uint8_t NK_get_major_firmware_version();
/**
* Get device's minor firmware version
* @return minor part of the version number (e.g. 7 from 0.7, 48 from 0.48 etc.)
*/
- NK_C_API int NK_get_minor_firmware_version();
+ NK_C_API uint8_t NK_get_minor_firmware_version();
/**
* Function to determine unencrypted volume PIN type
@@ -789,6 +834,17 @@ extern "C" {
NK_C_API int NK_get_status_storage(struct NK_storage_status* out);
/**
+ * Get SD card usage attributes. Usable during hidden volumes creation.
+ * If the command was successful (return value 0), the usage data is
+ * written to the output pointer’s target. The output pointer must
+ * not be null.
+ * Storage only
+ * @param out the output pointer for the usage data
+ * @return command processing error code
+ */
+ NK_C_API int NK_get_SD_usage_data(struct NK_SD_usage_data* out);
+
+ /**
* Get SD card usage attributes as string.
* Usable during hidden volumes creation.
* Storage only
@@ -799,7 +855,8 @@ extern "C" {
/**
* Get progress value of current long operation.
* Storage only
- * @return int in range 0-100 or -1 if device is not busy
+ * @return int in range 0-100 or -1 if device is not busy or -2 if an
+ * error occured
*/
NK_C_API int NK_get_progress_bar_value();
@@ -820,6 +877,19 @@ extern "C" {
*/
NK_C_API char* NK_list_devices_by_cpuID();
+ /**
+ * Returns a linked list of all connected devices, or null if no devices
+ * are connected or an error occured. The linked list must be freed by
+ * calling NK_free_device_info.
+ * @return a linked list of all connected devices
+ */
+ NK_C_API struct NK_device_info* NK_list_devices();
+
+ /**
+ * Free a linked list returned by NK_list_devices.
+ * @param the linked list to free or null
+ */
+ NK_C_API void NK_free_device_info(struct NK_device_info* device_info);
/**
* Connects to the device with given ID. ID's list could be created with NK_list_devices_by_cpuID.
@@ -832,6 +902,14 @@ extern "C" {
NK_C_API int NK_connect_with_ID(const char* id);
/**
+ * Connects to a device with the given path. The path is a USB device
+ * path as returned by hidapi.
+ * @param path the device path
+ * @return 1 on successful connection, 0 otherwise
+ */
+ NK_C_API int NK_connect_with_path(const char* path);
+
+ /**
* Blink red and green LED alternatively and infinitely (until device is reconnected).
* @return command processing error code
*/
diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc
index a950e4b..0320ebb 100644
--- a/NitrokeyManager.cc
+++ b/NitrokeyManager.cc
@@ -29,6 +29,7 @@
#include "libnitrokey/misc.h"
#include <mutex>
#include "libnitrokey/cxx_semantics.h"
+#include "libnitrokey/misc.h"
#include <functional>
#include <stick10_commands.h>
@@ -105,11 +106,10 @@ using nitrokey::misc::strcpyT;
return true;
}
- std::vector<std::string> NitrokeyManager::list_devices(){
+ std::vector<DeviceInfo> NitrokeyManager::list_devices(){
std::lock_guard<std::mutex> lock(mex_dev_com_manager);
- auto p = make_shared<Stick20>();
- return p->enumerate(); // make static
+ return Device::enumerate();
}
std::vector<std::string> NitrokeyManager::list_devices_by_cpuID(){
@@ -127,11 +127,13 @@ using nitrokey::misc::strcpyT;
LOGD1("Enumerating devices");
std::vector<std::string> res;
- auto d = make_shared<Stick20>();
- const auto v = d->enumerate();
+ const auto v = Device::enumerate();
LOGD1("Discovering IDs");
- for (auto & p: v){
- d = make_shared<Stick20>();
+ for (auto & i: v){
+ if (i.m_deviceModel != DeviceModel::STORAGE)
+ continue;
+ auto p = i.m_path;
+ auto d = make_shared<Stick20>();
LOGD1( std::string("Found: ") + p );
d->set_path(p);
try{
@@ -215,7 +217,26 @@ using nitrokey::misc::strcpyT;
}
}
- auto p = make_shared<Stick20>();
+ auto info_ptr = hid_enumerate(NITROKEY_VID, 0);
+ auto first_info_ptr = info_ptr;
+ if (!info_ptr)
+ return false;
+
+ misc::Option<DeviceModel> model;
+ while (info_ptr && !model.has_value()) {
+ if (path == std::string(info_ptr->path)) {
+ model = product_id_to_model(info_ptr->product_id);
+ }
+ info_ptr = info_ptr->next;
+ }
+ hid_free_enumeration(first_info_ptr);
+
+ if (!model.has_value())
+ return false;
+
+ auto p = Device::create(model.value());
+ if (!p)
+ return false;
p->set_path(path);
if(!p->connect()) return false;
@@ -916,7 +937,7 @@ using nitrokey::misc::strcpyT;
return false;
}
- int NitrokeyManager::get_minor_firmware_version(){
+ uint8_t NitrokeyManager::get_minor_firmware_version(){
switch(device->get_device_model()){
case DeviceModel::PRO:{
auto status_p = GetStatus::CommandTransaction::run(device);
@@ -932,7 +953,7 @@ using nitrokey::misc::strcpyT;
}
return 0;
}
- int NitrokeyManager::get_major_firmware_version(){
+ uint8_t NitrokeyManager::get_major_firmware_version(){
switch(device->get_device_model()){
case DeviceModel::PRO:{
auto status_p = GetStatus::CommandTransaction::run(device);
diff --git a/README.md b/README.md
index 8390fb6..a3683c0 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
# libnitrokey
libnitrokey is a project to communicate with Nitrokey Pro and Storage devices in a clean and easy manner. Written in C++14, testable with `py.test` and `Catch` frameworks, with C API, Python access (through CFFI and C API, in future with Pybind11).
-The development of this project is aimed to make it itself a living documentation of communication protocol between host and the Nitrokey stick devices. The command packets' format is described here: [Pro v0.7](include/stick10_commands.h), [Pro v0.8](include/stick10_commands_0.8.h), [Storage](include/stick20_commands.h). Handling and additional operations are described here: [NitrokeyManager.cc](NitrokeyManager.cc).
+The development of this project is aimed to make it itself a living documentation of communication protocol between host and the Nitrokey stick devices. The command packets' format is described here: [Pro v0.7](libnitrokey/stick10_commands.h), [Pro v0.8](libnitrokey/stick10_commands_0.8.h), [Storage](libnitrokey/stick20_commands.h). Handling and additional operations are described here: [NitrokeyManager.cc](NitrokeyManager.cc).
A C++14 complying compiler is required due to heavy use of variable templates. For feature support tables please check [table 1](https://gcc.gnu.org/projects/cxx-status.html#cxx14) or [table 2](http://en.cppreference.com/w/cpp/compiler_support).
@@ -164,7 +164,7 @@ All available functions for C and Python are listed in [NK_C_API.h](NK_C_API.h).
## Documentation
The documentation of C API is included in the sources (can be generated with `make doc` if Doxygen is installed).
-Please check [NK_C_API.h](NK_C_API.h) (C API) for high level commands and [include/NitrokeyManager.h](include/NitrokeyManager.h) (C++ API). All devices' commands are listed along with packet format in [include/stick10_commands.h](include/stick10_commands.h) and [include/stick20_commands.h](include/stick20_commands.h) respectively for Nitrokey Pro and Nitrokey Storage products.
+Please check [NK_C_API.h](NK_C_API.h) (C API) for high level commands and [libnitrokey/NitrokeyManager.h](libnitrokey/NitrokeyManager.h) (C++ API). All devices' commands are listed along with packet format in [libnitrokey/stick10_commands.h](libnitrokey/stick10_commands.h) and [libnitrokey/stick20_commands.h](libnitrokey/stick20_commands.h) respectively for Nitrokey Pro and Nitrokey Storage products.
# Tests
**Warning!** Most of the tests will overwrite user data. The only user-data safe tests are specified in `unittest/test_safe.cpp` (see *C++ tests* chapter).
diff --git a/device.cc b/device.cc
index 80e4b38..bc42965 100644
--- a/device.cc
+++ b/device.cc
@@ -20,7 +20,9 @@
*/
#include <chrono>
+#include <codecvt>
#include <iostream>
+#include <locale>
#include <thread>
#include <cstddef>
#include <stdexcept>
@@ -36,11 +38,42 @@ std::mutex mex_dev_com;
using namespace nitrokey::device;
using namespace nitrokey::log;
+using namespace nitrokey::misc;
using namespace std::chrono;
+const uint16_t nitrokey::device::NITROKEY_VID = 0x20a0;
+const uint16_t nitrokey::device::NITROKEY_PRO_PID = 0x4108;
+const uint16_t nitrokey::device::NITROKEY_STORAGE_PID = 0x4109;
+
+Option<DeviceModel> nitrokey::device::product_id_to_model(uint16_t product_id) {
+ switch (product_id) {
+ case NITROKEY_PRO_PID:
+ return DeviceModel::PRO;
+ case NITROKEY_STORAGE_PID:
+ return DeviceModel::STORAGE;
+ default:
+ return {};
+ }
+}
+
std::atomic_int Device::instances_count{0};
std::chrono::milliseconds Device::default_delay {0} ;
+std::ostream& nitrokey::device::operator<<(std::ostream& stream, DeviceModel model) {
+ switch (model) {
+ case DeviceModel::PRO:
+ stream << "Pro";
+ break;
+ case DeviceModel::STORAGE:
+ stream << "Storage";
+ break;
+ default:
+ stream << "Unknown";
+ break;
+ }
+ return stream;
+}
+
Device::Device(const uint16_t vid, const uint16_t pid, const DeviceModel model,
const milliseconds send_receive_delay, const int retry_receiving_count,
const milliseconds retry_timeout)
@@ -171,14 +204,20 @@ int Device::recv(void *packet) {
return status;
}
-std::vector<std::string> Device::enumerate(){
- //TODO make static
- auto pInfo = hid_enumerate(m_vid, m_pid);
+std::vector<DeviceInfo> Device::enumerate(){
+ auto pInfo = hid_enumerate(NITROKEY_VID, 0);
auto pInfo_ = pInfo;
- std::vector<std::string> res;
+ std::vector<DeviceInfo> res;
while (pInfo != nullptr){
- std::string a (pInfo->path);
- res.push_back(a);
+ auto deviceModel = product_id_to_model(pInfo->product_id);
+ if (deviceModel.has_value()) {
+ std::string path(pInfo->path);
+ std::wstring serialNumberW(pInfo->serial_number);
+ std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
+ std::string serialNumber = converter.to_bytes(serialNumberW);
+ DeviceInfo info = { deviceModel.value(), path, serialNumber };
+ res.push_back(info);
+ }
pInfo = pInfo->next;
}
@@ -189,6 +228,17 @@ std::vector<std::string> Device::enumerate(){
return res;
}
+std::shared_ptr<Device> Device::create(DeviceModel model) {
+ switch (model) {
+ case DeviceModel::PRO:
+ return std::make_shared<Stick10>();
+ case DeviceModel::STORAGE:
+ return std::make_shared<Stick20>();
+ default:
+ return {};
+ }
+}
+
bool Device::could_be_enumerated() {
LOG(__FUNCTION__, Loglevel::DEBUG_L2);
std::lock_guard<std::mutex> lock(mex_dev_com);
@@ -243,14 +293,14 @@ void Device::set_retry_delay(const std::chrono::milliseconds delay){
}
Stick10::Stick10():
- Device(0x20a0, 0x4108, DeviceModel::PRO, 100ms, 5, 100ms)
+ Device(NITROKEY_VID, NITROKEY_PRO_PID, DeviceModel::PRO, 100ms, 5, 100ms)
{
setDefaultDelay();
}
Stick20::Stick20():
- Device(0x20a0, 0x4109, DeviceModel::STORAGE, 40ms, 55, 40ms)
+ Device(NITROKEY_VID, NITROKEY_STORAGE_PID, DeviceModel::STORAGE, 40ms, 55, 40ms)
{
setDefaultDelay();
}
diff --git a/libnitrokey/NitrokeyManager.h b/libnitrokey/NitrokeyManager.h
index d6e5df4..2d8d1b6 100644
--- a/libnitrokey/NitrokeyManager.h
+++ b/libnitrokey/NitrokeyManager.h
@@ -80,7 +80,7 @@ char * strndup(const char* str, size_t maxlen);
bool get_time(uint64_t time = 0);
bool erase_totp_slot(uint8_t slot_number, const char *temporary_password);
bool erase_hotp_slot(uint8_t slot_number, const char *temporary_password);
- std::vector<std::string> list_devices();
+ std::vector<DeviceInfo> list_devices();
std::vector<std::string> list_devices_by_cpuID();
/**
@@ -215,7 +215,7 @@ char * strndup(const char* str, size_t maxlen);
template <typename S, typename A, typename T>
void authorize_packet(T &package, const char *admin_temporary_password, shared_ptr<Device> device);
- int get_minor_firmware_version();
+ uint8_t get_minor_firmware_version();
explicit NitrokeyManager();
void set_log_function(std::function<void(std::string)> log_function);
@@ -278,7 +278,7 @@ char * strndup(const char* str, size_t maxlen);
*/
void set_encrypted_volume_read_write(const char *admin_pin);
- int get_major_firmware_version();
+ uint8_t get_major_firmware_version();
bool is_smartcard_in_use();
diff --git a/libnitrokey/device.h b/libnitrokey/device.h
index f6d2380..d50080d 100644
--- a/libnitrokey/device.h
+++ b/libnitrokey/device.h
@@ -24,8 +24,11 @@
#include <chrono>
#include "hidapi/hidapi.h"
#include <cstdint>
+#include <memory>
#include <string>
+#include <ostream>
#include <vector>
+#include "misc.h"
#define HID_REPORT_SIZE 65
@@ -50,6 +53,48 @@ enum class DeviceModel{
STORAGE
};
+std::ostream& operator<<(std::ostream& stream, DeviceModel model);
+
+/**
+ * The USB vendor ID for Nitrokey devices.
+ */
+extern const uint16_t NITROKEY_VID;
+/**
+ * The USB product ID for the Nitrokey Pro.
+ */
+extern const uint16_t NITROKEY_PRO_PID;
+/**
+ * The USB product ID for the Nitrokey Storage.
+ */
+extern const uint16_t NITROKEY_STORAGE_PID;
+
+/**
+ * Convert the given USB product ID to a Nitrokey model. If there is no model
+ * with that ID, return an absent value.
+ */
+misc::Option<DeviceModel> product_id_to_model(uint16_t product_id);
+
+/**
+ * Information about a connected device.
+ *
+ * This struct contains the information about a connected device returned by
+ * hidapi when enumerating the connected devices.
+ */
+struct DeviceInfo {
+ /**
+ * The model of the connected device.
+ */
+ DeviceModel m_deviceModel;
+ /**
+ * The USB connection path for the device.
+ */
+ std::string m_path;
+ /**
+ * The serial number of the device.
+ */
+ std::string m_serialNumber;
+};
+
#include <atomic>
class Device {
@@ -106,7 +151,17 @@ public:
* @return true if visible by OS
*/
bool could_be_enumerated();
- std::vector<std::string> enumerate();
+ /**
+ * Returns a vector with all connected Nitrokey devices.
+ *
+ * @return information about all connected devices
+ */
+ static std::vector<DeviceInfo> enumerate();
+
+ /**
+ * Create a Device of the given model.
+ */
+ static std::shared_ptr<Device> create(DeviceModel model);
void show_stats();
diff --git a/libnitrokey/misc.h b/libnitrokey/misc.h
index 88254dd..d10c8df 100644
--- a/libnitrokey/misc.h
+++ b/libnitrokey/misc.h
@@ -29,12 +29,37 @@
#include "log.h"
#include "LibraryException.h"
#include <sstream>
+#include <stdexcept>
#include <iomanip>
namespace nitrokey {
namespace misc {
+/**
+ * Simple replacement for std::optional (C++17).
+ */
+template<typename T>
+class Option {
+public:
+ Option() : m_hasValue(false), m_value() {}
+ Option(T value) : m_hasValue(true), m_value(value) {}
+
+ bool has_value() const {
+ return m_hasValue;
+ }
+ T value() const {
+ if (!m_hasValue) {
+ throw std::logic_error("Called Option::value without value");
+ }
+ return m_value;
+ }
+
+private:
+ bool m_hasValue;
+ T m_value;
+};
+
template<typename T>
std::string toHex(T value){
using namespace std;
diff --git a/unittest/conftest.py b/unittest/conftest.py
index 253e1d8..a9f46c6 100644
--- a/unittest/conftest.py
+++ b/unittest/conftest.py
@@ -21,10 +21,17 @@ SPDX-License-Identifier: LGPL-3.0
import pytest
-from misc import ffi
+from misc import ffi, gs
device_type = None
+from logging import getLogger, basicConfig, DEBUG
+
+basicConfig(format='* %(relativeCreated)6d %(filename)s:%(lineno)d %(message)s',level=DEBUG)
+log = getLogger('conftest')
+print = log.debug
+
+
def skip_if_device_version_lower_than(allowed_devices):
global device_type
model, version = device_type
@@ -33,8 +40,45 @@ def skip_if_device_version_lower_than(allowed_devices):
pytest.skip('This device model is not applicable to run this test')
+class AtrrCallProx(object):
+ def __init__(self, C, name):
+ self.C = C
+ self.name = name
+
+ def __call__(self, *args, **kwargs):
+ print('Calling {}{}'.format(self.name, args))
+ res = self.C(*args, **kwargs)
+ res_s = res
+ try:
+ res_s = '{} => '.format(res) + '{}'.format(gs(res))
+ except Exception as e:
+ pass
+ print('Result of {}: {}'.format(self.name, res_s))
+ return res
+
+
+class AttrProxy(object):
+ def __init__(self, C, name):
+ self.C = C
+ self.name = name
+
+ def __getattr__(self, attr):
+ return AtrrCallProx(getattr(self.C, attr), attr)
+
+
+@pytest.fixture(scope="module")
+def C_offline(request=None):
+ print("Getting library without initializing connection")
+ return get_library(request, allow_offline=True)
+
+
@pytest.fixture(scope="module")
def C(request=None):
+ print("Getting library with connection initialized")
+ return get_library(request)
+
+
+def get_library(request, allow_offline=False):
fp = '../NK_C_API.h'
declarations = []
@@ -44,13 +88,13 @@ def C(request=None):
cnt = 0
a = iter(declarations)
for declaration in a:
- if declaration.strip().startswith('NK_C_API'):
+ if declaration.strip().startswith('NK_C_API') \
+ or declaration.strip().startswith('struct'):
declaration = declaration.replace('NK_C_API', '').strip()
- while ';' not in declaration:
- declaration += (next(a)).strip()
- # print(declaration)
+ while ');' not in declaration and '};' not in declaration:
+ declaration += (next(a)).strip()+'\n'
ffi.cdef(declaration, override=True)
- cnt +=1
+ cnt += 1
print('Imported {} declarations'.format(cnt))
C = None
@@ -82,12 +126,13 @@ def C(request=None):
nk_login = C.NK_login_auto()
if nk_login != 1:
print('No devices detected!')
- assert nk_login != 0 # returns 0 if not connected or wrong model or 1 when connected
- global device_type
- firmware_version = C.NK_get_minor_firmware_version()
- model = 'P' if firmware_version < 20 else 'S'
- device_type = (model, firmware_version)
- print('Connected device: {} {}'.format(model, firmware_version))
+ if not allow_offline:
+ assert nk_login != 0 # returns 0 if not connected or wrong model or 1 when connected
+ global device_type
+ firmware_version = C.NK_get_minor_firmware_version()
+ model = 'P' if firmware_version < 20 else 'S'
+ device_type = (model, firmware_version)
+ print('Connected device: {} {}'.format(model, firmware_version))
# assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
# assert C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP) == DeviceErrorCode.STATUS_OK
@@ -104,4 +149,5 @@ def C(request=None):
# C.NK_set_debug(True)
C.NK_set_debug_level(int(os.environ.get('LIBNK_DEBUG', 3)))
- return C
+ return AttrProxy(C, "libnitrokey C")
+
diff --git a/unittest/requirements.txt b/unittest/requirements.txt
index 5c0110b..6d718ad 100644
--- a/unittest/requirements.txt
+++ b/unittest/requirements.txt
@@ -1,4 +1,5 @@
cffi
pytest-repeat
pytest-randomly
-oath \ No newline at end of file
+tqdm
+oath
diff --git a/unittest/test_multiple.py b/unittest/test_multiple.py
index 3f1d2d5..821a3b7 100644
--- a/unittest/test_multiple.py
+++ b/unittest/test_multiple.py
@@ -29,13 +29,40 @@ from tqdm import tqdm
from conftest import skip_if_device_version_lower_than
from constants import DefaultPasswords, DeviceErrorCode, bb
-from misc import gs, wait
+from misc import gs, wait, ffi
pprint = pprint.PrettyPrinter(indent=4).pprint
@pytest.mark.other
@pytest.mark.info
+def test_list_devices(C):
+ infos = C.NK_list_devices()
+ assert infos != ffi.NULL
+ C.NK_free_device_info(infos)
+
+
+@pytest.mark.other
+@pytest.mark.info
+def test_connect_with_path(C):
+ ids = gs(C.NK_list_devices_by_cpuID())
+ # NK_list_devices_by_cpuID already opened the devices, so we have to close
+ # them before trying to reconnect
+ assert C.NK_logout() == 0
+
+ devices_list = ids.split(b';')
+ for value in devices_list:
+ parts = value.split(b'_p_')
+ assert len(parts) < 3
+ if len(parts) == 2:
+ path = parts[1]
+ else:
+ path = parts[0]
+ assert C.NK_connect_with_path(path) == 1
+
+
+@pytest.mark.other
+@pytest.mark.info
def test_get_status_storage_multiple(C):
ids = gs(C.NK_list_devices_by_cpuID())
print(ids)
diff --git a/unittest/test_multiple_devices.cc b/unittest/test_multiple_devices.cc
index cd78681..4b1e2c1 100644
--- a/unittest/test_multiple_devices.cc
+++ b/unittest/test_multiple_devices.cc
@@ -29,15 +29,40 @@ const char * RFC_SECRET = "12345678901234567890";
#include <iostream>
#include <NitrokeyManager.h>
#include <stick20_commands.h>
+#include "../NK_C_API.h"
using namespace nitrokey;
TEST_CASE("List devices", "[BASIC]") {
+ auto v = Device::enumerate();
+ REQUIRE(v.size() > 0);
+ for (auto i : v){
+ auto d = Device::create(i.m_deviceModel);
+ if (!d) {
+ std::cout << "Could not create device with model " << i.m_deviceModel << "\n";
+ continue;
+ }
+ std::cout << i.m_deviceModel << " " << i.m_path << " "
+ << i.m_serialNumber << " |";
+ d->set_path(i.m_path);
+ d->connect();
+ auto res = GetStatus::CommandTransaction::run(d);
+ std::cout << " " << res.data().card_serial_u32 << " "
+ << res.data().get_card_serial_hex()
+ << std::endl;
+ d->disconnect();
+ }
+}
+
+TEST_CASE("List Storage devices", "[BASIC]") {
shared_ptr<Stick20> d = make_shared<Stick20>();
- auto v = d->enumerate();
+ auto v = Device::enumerate();
REQUIRE(v.size() > 0);
- for (auto a : v){
+ for (auto i : v){
+ if (i.m_deviceModel != DeviceModel::STORAGE)
+ continue;
+ auto a = i.m_path;
std::cout << a;
d->set_path(a);
d->connect();
@@ -53,11 +78,14 @@ TEST_CASE("List devices", "[BASIC]") {
TEST_CASE("Regenerate AES keys", "[BASIC]") {
shared_ptr<Stick20> d = make_shared<Stick20>();
- auto v = d->enumerate();
+ auto v = Device::enumerate();
REQUIRE(v.size() > 0);
std::vector<shared_ptr<Stick20>> devices;
- for (auto a : v){
+ for (auto i : v){
+ if (i.m_deviceModel != DeviceModel::STORAGE)
+ continue;
+ auto a = i.m_path;
std::cout << a << endl;
d = make_shared<Stick20>();
d->set_path(a);
@@ -81,14 +109,57 @@ TEST_CASE("Regenerate AES keys", "[BASIC]") {
}
+TEST_CASE("Use C API", "[BASIC]") {
+ auto ptr = NK_list_devices();
+ auto first_ptr = ptr;
+ REQUIRE(ptr != nullptr);
+
+ while (ptr) {
+ std::cout << "Connect with: " << ptr->model << " " << ptr->path << " "
+ << ptr->serial_number << " | " << NK_connect_with_path(ptr->path) << " | ";
+ auto status = NK_status();
+ std::cout << status << std::endl;
+ free(status);
+ ptr = ptr->next;
+ }
+
+ NK_free_device_info(first_ptr);
+}
+
+
TEST_CASE("Use API", "[BASIC]") {
auto nm = NitrokeyManager::instance();
nm->set_loglevel(2);
auto v = nm->list_devices();
REQUIRE(v.size() > 0);
+ for (auto i : v) {
+ std::cout << "Connect with: " << i.m_deviceModel << " " << i.m_path << " "
+ << i.m_serialNumber << " | " << std::boolalpha << nm->connect_with_path(i.m_path) << " |";
+ try {
+ auto status = nm->get_status();
+ std::cout << " " << status.card_serial_u32 << " "
+ << status.get_card_serial_hex()
+ << std::endl;
+ } catch (const LongOperationInProgressException &e) {
+ std::cout << "long operation in progress on " << i.m_path
+ << " " << std::to_string(e.progress_bar_value) << std::endl;
+ }
+ }
+}
+
+
+TEST_CASE("Use Storage API", "[BASIC]") {
+ auto nm = NitrokeyManager::instance();
+ nm->set_loglevel(2);
+ auto v = nm->list_devices();
+ REQUIRE(v.size() > 0);
+
for (int i=0; i<10; i++){
- for (auto a : v) {
+ for (auto i : v) {
+ if (i.m_deviceModel != DeviceModel::STORAGE)
+ continue;
+ auto a = i.m_path;
std::cout <<"Connect with: " << a <<
" " << std::boolalpha << nm->connect_with_path(a) << " ";
try{
diff --git a/unittest/test_offline.py b/unittest/test_offline.py
new file mode 100644
index 0000000..51fe67d
--- /dev/null
+++ b/unittest/test_offline.py
@@ -0,0 +1,39 @@
+"""
+Copyright (c) 2019 Nitrokey UG
+
+This file is part of libnitrokey.
+
+libnitrokey is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+any later version.
+
+libnitrokey is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with libnitrokey. If not, see <http://www.gnu.org/licenses/>.
+
+SPDX-License-Identifier: LGPL-3.0
+"""
+
+from misc import gs
+import re
+
+
+def test_offline(C_offline):
+ C_offline.NK_set_debug(False)
+ C_offline.NK_set_debug_level(4)
+ assert C_offline.NK_get_major_library_version() == 3
+ assert C_offline.NK_get_minor_library_version() >= 3
+ assert C_offline.NK_login_auto() == 0
+
+ libnk_version = gs(C_offline.NK_get_library_version())
+ assert libnk_version
+ print(libnk_version)
+
+ # v3.4.1-29-g1f3d
+ search = re.search(b'v\d\.\d(\.\d)?', libnk_version)
+ assert search is not None \ No newline at end of file
diff --git a/unittest/test_storage.py b/unittest/test_storage.py
index 2aa8441..04b0581 100644
--- a/unittest/test_storage.py
+++ b/unittest/test_storage.py
@@ -24,7 +24,8 @@ import pytest
from conftest import skip_if_device_version_lower_than
from constants import DefaultPasswords, DeviceErrorCode, bb
-from misc import gs, wait
+from misc import gs, wait, ffi
+
pprint = pprint.PrettyPrinter(indent=4).pprint
@@ -367,3 +368,30 @@ def test_send_startup(C):
skip_if_device_version_lower_than({'S': 43})
time_seconds_from_epoch = 0 # FIXME set proper date
assert C.NK_send_startup(time_seconds_from_epoch) == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.other
+def test_struct_multiline_prodtest(C):
+ info_st = ffi.new('struct NK_storage_ProductionTest *')
+ if info_st is None: raise Exception('Invalid value')
+ err = C.NK_get_storage_production_info(info_st)
+ assert err == 0
+ assert info_st.SD_Card_ManufacturingYear_u8 != 0
+ assert info_st.SD_Card_ManufacturingMonth_u8 != 0
+ assert info_st.SD_Card_Size_u8 != 0
+ assert info_st.FirmwareVersion_au8[0] == 0
+ assert info_st.FirmwareVersion_au8[1] >= 50
+
+ info = 'CPU:{CPU},SC:{SC},SD:{SD},' \
+ 'SCM:{SCM},SCO:{SCO},DAT:{DAT},Size:{size},Firmware:{fw} - {fwb}'.format(
+ CPU='0x{:08x}'.format(info_st.CPU_CardID_u32),
+ SC='0x{:08x}'.format(info_st.SmartCardID_u32),
+ SD='0x{:08x}'.format(info_st.SD_CardID_u32),
+ SCM='0x{:02x}'.format(info_st.SD_Card_Manufacturer_u8),
+ SCO='0x{:04x}'.format(info_st.SD_Card_OEM_u16),
+ DAT='20{}.{}'.format(info_st.SD_Card_ManufacturingYear_u8, info_st.SD_Card_ManufacturingMonth_u8),
+ size=info_st.SD_Card_Size_u8,
+ fw='{}.{}'.format(info_st.FirmwareVersion_au8[0], info_st.FirmwareVersion_au8[1]),
+ fwb=info_st.FirmwareVersionInternal_u8
+ )
+ print(info)