summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSzczepan Zalega <szczepan@nitrokey.com>2019-01-15 16:02:31 +0100
committerSzczepan Zalega <szczepan@nitrokey.com>2019-01-15 16:02:31 +0100
commite853275784e48823be0f39f0855efbe85c0f905c (patch)
tree2b428a23bddbd87c08ee47351bc31768f54b63fe
parent3f3fdebbc795dc3805cd5be105ce994286598f16 (diff)
parentcd1cfdbfc4113186f80dbadf5eb76543b22e34bd (diff)
downloadlibnitrokey-e853275784e48823be0f39f0855efbe85c0f905c.tar.gz
libnitrokey-e853275784e48823be0f39f0855efbe85c0f905c.tar.bz2
Merge branch 'pr_138' into contributions
Improve support for multiple devices Fixes #138
-rw-r--r--NK_C_API.cc68
-rw-r--r--NK_C_API.h44
-rw-r--r--NitrokeyManager.cc37
-rw-r--r--device.cc66
-rw-r--r--libnitrokey/NitrokeyManager.h2
-rw-r--r--libnitrokey/device.h57
-rw-r--r--libnitrokey/misc.h25
-rw-r--r--unittest/test_multiple.py29
-rw-r--r--unittest/test_multiple_devices.cc81
9 files changed, 385 insertions, 24 deletions
diff --git a/NK_C_API.cc b/NK_C_API.cc
index a780acf..794673a 100644
--- a/NK_C_API.cc
+++ b/NK_C_API.cc
@@ -753,6 +753,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([&]() {
@@ -760,6 +820,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 520d5e0..3effbfe 100644
--- a/NK_C_API.h
+++ b/NK_C_API.h
@@ -57,6 +57,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.
*/
@@ -797,6 +820,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.
@@ -809,6 +845,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..99f0b7a 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;
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..6908143 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();
/**
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/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{