From 2faa8f6782a2e6294ed8849048a281d12d60da1c Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Mon, 4 Dec 2017 16:29:56 +0100 Subject: Initial support for multiple devices with C++ and test Signed-off-by: Szczepan Zalega --- .idea/vcs.xml | 3 -- CMakeLists.txt | 3 ++ NitrokeyManager.cc | 30 +++++++++++++ device.cc | 29 +++++++++++- include/NitrokeyManager.h | 7 ++- include/device.h | 10 ++++- unittest/test_multiple_devices.cc | 92 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 167 insertions(+), 7 deletions(-) create mode 100644 unittest/test_multiple_devices.cc diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 486a99a..94a25f7 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,8 +2,5 @@ - - - \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a4ebb0..3d788cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,6 +191,9 @@ IF (COMPILE_TESTS) add_executable (test_issues unittest/test_issues.cc) target_link_libraries (test_issues ${EXTRA_LIBS} nitrokey catch) + add_executable (test_multiple_devices unittest/test_multiple_devices.cc) + target_link_libraries (test_multiple_devices ${EXTRA_LIBS} ${LIBNAME} catch) + ENDIF() diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index d563b26..da5f61a 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -81,6 +81,11 @@ using nitrokey::misc::strcpyT; set_debug(true); } NitrokeyManager::~NitrokeyManager() { + for (auto d : connected_devices){ + if (d.second == nullptr) continue; + d.second->disconnect(); + connected_devices[d.first] = nullptr; + } } bool NitrokeyManager::set_current_device_speed(int retry_delay, int send_receive_delay){ @@ -98,6 +103,31 @@ using nitrokey::misc::strcpyT; return true; } + std::vector NitrokeyManager::list_devices(){ + auto p = make_shared(); + return p->enumerate(); // make static + } + + bool NitrokeyManager::connect_with_path(std::string path) { + std::lock_guard lock(mex_dev_com_manager); + + if(connected_devices.find(path) != connected_devices.end() + && connected_devices[path] != nullptr) { + device = connected_devices[path]; + return true; + } + + + auto p = make_shared(); + p->set_path(path); + + if(!p->connect()) return false; + + connected_devices [path] = p; + device = p; + return true; + } + bool NitrokeyManager::connect() { std::lock_guard lock(mex_dev_com_manager); vector< shared_ptr > devices = { make_shared(), make_shared() }; diff --git a/device.cc b/device.cc index ac31b1d..5a83e2e 100644 --- a/device.cc +++ b/device.cc @@ -20,6 +20,7 @@ */ #include +#include #include #include #include @@ -92,12 +93,20 @@ bool Device::_connect() { LOG(std::string(__FUNCTION__) + std::string(" *IN* "), Loglevel::DEBUG_L2); // hid_init(); // done automatically on hid_open - mp_devhandle = hid_open(m_vid, m_pid, nullptr); + if (m_path.empty()){ + mp_devhandle = hid_open(m_vid, m_pid, nullptr); + } else { + mp_devhandle = hid_open_path(m_path.c_str()); + } const bool success = mp_devhandle != nullptr; LOG(std::string("Connection success: ") + std::to_string(success), Loglevel::DEBUG_L2); return success; } +void Device::set_path(const std::string path){ + m_path = path; +} + int Device::send(const void *packet) { LOG(__FUNCTION__, Loglevel::DEBUG_L2); std::lock_guard lock(mex_dev_com); @@ -160,6 +169,24 @@ int Device::recv(void *packet) { return status; } +std::vector Device::enumerate(){ + //TODO make static + auto pInfo = hid_enumerate(m_vid, m_pid); + auto pInfo_ = pInfo; + std::vector res; + while (pInfo != nullptr){ + std::string a (pInfo->path); + res.push_back(a); + pInfo = pInfo->next; + } + + if (pInfo_ != nullptr){ + hid_free_enumeration(pInfo_); + } + + return res; +} + bool Device::could_be_enumerated() { LOG(__FUNCTION__, Loglevel::DEBUG_L2); std::lock_guard lock(mex_dev_com); diff --git a/include/NitrokeyManager.h b/include/NitrokeyManager.h index 0db0856..2a79922 100644 --- a/include/NitrokeyManager.h +++ b/include/NitrokeyManager.h @@ -30,6 +30,7 @@ #include "stick20_commands.h" #include #include +#include namespace nitrokey { using namespace nitrokey::device; @@ -67,6 +68,8 @@ 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 list_devices(); + bool connect_with_path (std::string path); bool connect(const char *device_model); bool connect(); bool disconnect(); @@ -198,8 +201,10 @@ char * strndup(const char* str, size_t maxlen); static shared_ptr _instance; std::shared_ptr device; + std::unordered_map > connected_devices; - stick10::ReadSlot::ResponsePayload get_OTP_slot_data(const uint8_t slot_number); + + stick10::ReadSlot::ResponsePayload get_OTP_slot_data(const uint8_t slot_number); bool is_valid_hotp_slot_number(uint8_t slot_number) const; bool is_valid_totp_slot_number(uint8_t slot_number) const; bool is_valid_password_safe_slot_number(uint8_t slot_number) const; diff --git a/include/device.h b/include/device.h index 1bd4773..f6d2380 100644 --- a/include/device.h +++ b/include/device.h @@ -25,6 +25,7 @@ #include "hidapi/hidapi.h" #include #include +#include #define HID_REPORT_SIZE 65 @@ -105,8 +106,10 @@ public: * @return true if visible by OS */ bool could_be_enumerated(); + std::vector enumerate(); - void show_stats(); + + void show_stats(); // ErrorCounters get_stats(){ return m_counters; } int get_retry_receiving_count() const { return m_retry_receiving_count; }; int get_retry_sending_count() const { return m_retry_sending_count; }; @@ -121,8 +124,10 @@ public: void set_retry_delay(std::chrono::milliseconds delay); static void set_default_device_speed(int delay); void setDefaultDelay(); + void set_path(const std::string path); + -private: + private: std::atomic last_command_status; void _reconnect(); bool _connect(); @@ -143,6 +148,7 @@ protected: std::chrono::milliseconds m_retry_timeout; std::chrono::milliseconds m_send_receive_delay; std::atomicmp_devhandle; + std::string m_path; static std::atomic_int instances_count; static std::chrono::milliseconds default_delay ; diff --git a/unittest/test_multiple_devices.cc b/unittest/test_multiple_devices.cc new file mode 100644 index 0000000..f5b4d6e --- /dev/null +++ b/unittest/test_multiple_devices.cc @@ -0,0 +1,92 @@ + +static const char *const default_admin_pin = "12345678"; +static const char *const default_user_pin = "123456"; +const char * temporary_password = "123456789012345678901234"; +const char * RFC_SECRET = "12345678901234567890"; + +#include "catch.hpp" + +#include +#include +#include +#include + +using namespace nitrokey; + + +TEST_CASE("List devices", "[BASIC]") { + shared_ptr d = make_shared(); + auto v = d->enumerate(); + REQUIRE(v.size() > 0); + for (auto a : v){ + std::cout << a; + d->set_path(a); + d->connect(); + auto res = GetStatus::CommandTransaction::run(d); + auto res2 = GetDeviceStatus::CommandTransaction::run(d); + std::cout << " " << res.data().card_serial_u32 << " " + << res.data().get_card_serial_hex() + << " " << std::to_string(res2.data().versionInfo.minor) + << std::endl; + d->disconnect(); + } +} + +TEST_CASE("Regenerate AES keys", "[BASIC]") { + shared_ptr d = make_shared(); + auto v = d->enumerate(); + REQUIRE(v.size() > 0); + + std::vector> devices; + for (auto a : v){ + std::cout << a << endl; + d = make_shared(); + d->set_path(a); + d->connect(); + devices.push_back(d); + } + + for (auto d : devices){ + auto res2 = GetDeviceStatus::CommandTransaction::run(d); + std::cout << std::to_string(res2.data().versionInfo.minor) << std::endl; +// nitrokey::proto::stick20::CreateNewKeys::CommandPayload p; +// p.set_defaults(); +// memcpy(p.password, "12345678", 8); +// auto res3 = nitrokey::proto::stick20::CreateNewKeys::CommandTransaction::run(d, p); + } + + for (auto d : devices){ + //TODO watch out for multiple hid_exit calls + d->disconnect(); + } +} + + +TEST_CASE("Use 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) { + std::cout <<"Connect with: " << a << + " " << std::boolalpha << nm->connect_with_path(a) << " "; + try{ + auto status_storage = nm->get_status_storage(); + std::cout << status_storage.ActiveSmartCardID_u32 + << " " << status_storage.ActiveSD_CardID_u32 + << std::endl; + + nm->fill_SD_card_with_random_data("12345678"); + } + catch (const LongOperationInProgressException &e){ + std::cout << "long operation in progress on " << a + << " " << std::to_string(e.progress_bar_value) << std::endl; + this_thread::sleep_for(1000ms); + } + } + std::cout <<"Iteration: " << i << std::endl; + } + +} \ No newline at end of file -- cgit v1.2.3 From 388cf5fcb33f24bc04f79cd1fbea980214518d54 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Mon, 4 Dec 2017 19:21:09 +0100 Subject: List devices by unique SC:SD id Add C API and tests Add mutexes Signed-off-by: Szczepan Zalega --- NK_C_API.cc | 21 ++++++++++ NK_C_API.h | 6 +++ NitrokeyManager.cc | 87 +++++++++++++++++++++++++++++++++++---- include/NitrokeyManager.h | 3 ++ unittest/test_C_API.cpp | 28 +++++++++++++ unittest/test_multiple_devices.cc | 24 ++++++++++- 6 files changed, 160 insertions(+), 9 deletions(-) diff --git a/NK_C_API.cc b/NK_C_API.cc index f881caf..59247ba 100644 --- a/NK_C_API.cc +++ b/NK_C_API.cc @@ -608,6 +608,27 @@ extern "C" { }); } + NK_C_API const char* NK_list_devices_by_cpuID() { + auto nm = NitrokeyManager::instance(); + return get_with_string_result([&]() { + auto v = nm->list_devices_by_cpuID(); + std::string res; + for (const auto a : v){ + res += a+";"; + } + if (res.size()>0) res.pop_back(); // remove last delimeter char + return strndup(res.c_str(), 4096); + }); + } + + NK_C_API int NK_connect_with_ID(const char* id) { + auto m = NitrokeyManager::instance(); + return get_with_result([&]() { + return m->connect_with_ID(id) ? 1 : 0; + }); + } + + #ifdef __cplusplus } diff --git a/NK_C_API.h b/NK_C_API.h index 73022b2..5933c67 100644 --- a/NK_C_API.h +++ b/NK_C_API.h @@ -568,6 +568,12 @@ extern "C" { */ NK_C_API int NK_get_progress_bar_value(); + + NK_C_API const char* NK_list_devices_by_cpuID(); + NK_C_API int NK_connect_with_ID(const char* id); + + + #ifdef __cplusplus } #endif diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index da5f61a..4935076 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -81,6 +81,8 @@ using nitrokey::misc::strcpyT; set_debug(true); } NitrokeyManager::~NitrokeyManager() { + std::lock_guard lock(mex_dev_com_manager); + for (auto d : connected_devices){ if (d.second == nullptr) continue; d.second->disconnect(); @@ -104,27 +106,98 @@ using nitrokey::misc::strcpyT; } std::vector NitrokeyManager::list_devices(){ + std::lock_guard lock(mex_dev_com_manager); + auto p = make_shared(); return p->enumerate(); // make static } - bool NitrokeyManager::connect_with_path(std::string path) { + std::vector NitrokeyManager::list_devices_by_cpuID(){ std::lock_guard lock(mex_dev_com_manager); - if(connected_devices.find(path) != connected_devices.end() - && connected_devices[path] != nullptr) { - device = connected_devices[path]; - return true; + std::vector res; + auto d = make_shared(); + const auto v = d->enumerate(); + for (auto & p: v){ + d = make_shared(); + d->set_path(p); + try{ + if (d->connect()){ + device = d; + const auto status = get_status_storage(); + const auto sc_id = status.ActiveSmartCardID_u32; + const auto sd_id = status.ActiveSD_CardID_u32; + + auto id = std::to_string(sc_id) + ":" + std::to_string(sd_id); + connected_devices_byID[id] = d; + res.push_back(id); + } else{ + std::cout << "Could not connect to: " + p << std::endl; + } + } + catch (const DeviceCommunicationException &e){ + //ignore + std::cout << p << ": " << " Exception encountered" << std::endl; + } } + return res; + } +/** + * Connect to the device using unique smartcard:datacard id. + * Needs list_device_by_cpuID run first + * @param id + * @return + */ + bool NitrokeyManager::connect_with_ID(const std::string id) { + std::lock_guard lock(mex_dev_com_manager); + auto position = connected_devices_byID.find(id); + if (position == connected_devices_byID.end()) return false; + + auto d = connected_devices_byID[id]; + device = d; + + try{ + get_status(); + } + catch (const DeviceCommunicationException &){ + d->disconnect(); + connected_devices_byID[id] = nullptr; + connected_devices_byID.erase(position); + return false; + } + return true; + } + + /** + * Connects device to path. + * Assumes devices are not being disconnected and caches connections (param cache_connections). + * @param path os-dependent device path + * @return false, when could not connect, true otherwise + */ + bool NitrokeyManager::connect_with_path(std::string path) { + const bool cache_connections = false; + + std::lock_guard lock(mex_dev_com_manager); + + if (cache_connections){ + if(connected_devices.find(path) != connected_devices.end() + && connected_devices[path] != nullptr) { + device = connected_devices[path]; + return true; + } + } auto p = make_shared(); p->set_path(path); if(!p->connect()) return false; - connected_devices [path] = p; - device = p; + if(cache_connections){ + connected_devices [path] = p; + } + + device = p; //previous device will be disconnected automatically return true; } diff --git a/include/NitrokeyManager.h b/include/NitrokeyManager.h index 2a79922..7ce432f 100644 --- a/include/NitrokeyManager.h +++ b/include/NitrokeyManager.h @@ -69,6 +69,8 @@ char * strndup(const char* str, size_t maxlen); 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 list_devices(); + std::vector list_devices_by_cpuID(); + bool connect_with_ID(const std::string id); bool connect_with_path (std::string path); bool connect(const char *device_model); bool connect(); @@ -202,6 +204,7 @@ char * strndup(const char* str, size_t maxlen); static shared_ptr _instance; std::shared_ptr device; std::unordered_map > connected_devices; + std::unordered_map > connected_devices_byID; stick10::ReadSlot::ResponsePayload get_OTP_slot_data(const uint8_t slot_number); diff --git a/unittest/test_C_API.cpp b/unittest/test_C_API.cpp index be47f08..2d83ef4 100644 --- a/unittest/test_C_API.cpp +++ b/unittest/test_C_API.cpp @@ -43,6 +43,7 @@ TEST_CASE("C API connect", "[BASIC]") { TEST_CASE("Check retry count", "[BASIC]") { REQUIRE(login != 0); + NK_set_debug_level(3); REQUIRE(NK_get_admin_retry_count() == 3); REQUIRE(NK_get_user_retry_count() == 3); } @@ -56,4 +57,31 @@ TEST_CASE("Check long strings", "[STANDARD]") { result = NK_change_user_PIN(pin, longPin); REQUIRE(result == TOO_LONG_STRING); CAPTURE(result); +} + +#include + +TEST_CASE("multiple devices with ID", "[BASIC]") { + NK_logout(); + NK_set_debug_level(3); + auto s = NK_list_devices_by_cpuID(); + REQUIRE(s!=nullptr); + REQUIRE(strnlen(s, 4096) < 4096); + REQUIRE(strnlen(s, 4096) > 2*4); + std::cout << s << std::endl; + + char *string, *token; + int t; + + string = strndup(s, 4096); + free ( (void*) s); + + while ((token = strsep(&string, ";")) != nullptr){ + if (strnlen(token, 4096) < 3) continue; + std::cout << token << " connecting: "; + std::cout << (t=NK_connect_with_ID(token)) << std::endl; + REQUIRE(t == 1); + } + + free (string); } \ No newline at end of file diff --git a/unittest/test_multiple_devices.cc b/unittest/test_multiple_devices.cc index f5b4d6e..235a24d 100644 --- a/unittest/test_multiple_devices.cc +++ b/unittest/test_multiple_devices.cc @@ -78,15 +78,35 @@ TEST_CASE("Use API", "[BASIC]") { << " " << status_storage.ActiveSD_CardID_u32 << std::endl; - nm->fill_SD_card_with_random_data("12345678"); +// nm->fill_SD_card_with_random_data("12345678"); } catch (const LongOperationInProgressException &e){ std::cout << "long operation in progress on " << a << " " << std::to_string(e.progress_bar_value) << std::endl; - this_thread::sleep_for(1000ms); +// this_thread::sleep_for(1000ms); } } std::cout <<"Iteration: " << i << std::endl; } +} + + +TEST_CASE("Use API ID", "[BASIC]") { + auto nm = NitrokeyManager::instance(); + nm->set_loglevel(2); + + auto v = nm->list_devices_by_cpuID(); + REQUIRE(v.size() > 0); + + for(int i=0; i<1000; i++) { + auto v = nm->list_devices_by_cpuID(); + REQUIRE(v.size() > 0); + for (auto i : v) { + nm->connect_with_ID(i); + auto retry_count = nm->get_admin_retry_count(); + std::cout << i << " " << to_string(retry_count) << std::endl; + } + } + std::cout << "finished" << std::endl; } \ No newline at end of file -- cgit v1.2.3 From 9300a50676c2dfabe7e63797c610b2d48d0065cf Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 8 Dec 2017 10:42:49 +0100 Subject: Do not disconnect device on getting status failure Signed-off-by: Szczepan Zalega --- NitrokeyManager.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index 4935076..e3e1730 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -336,7 +336,7 @@ using nitrokey::misc::strcpyT; return response.data(); } catch (DeviceSendingFailure &e){ - disconnect(); +// disconnect(); throw; } } -- cgit v1.2.3 From 9aa7bb5c6783d28d7d4ffe667fdfbc7400615587 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 8 Dec 2017 10:43:37 +0100 Subject: Add LOGD1 macro Signed-off-by: Szczepan Zalega --- include/log.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/log.h b/include/log.h index 52a6e16..89eda56 100644 --- a/include/log.h +++ b/include/log.h @@ -98,6 +98,7 @@ namespace nitrokey { #define LOGD(string) while(false){} #else #define LOG(string, level) nitrokey::log::Log::instance()((string), (level)) +#define LOGD1(string) nitrokey::log::Log::instance()((string), (nitrokey::log::Loglevel::DEBUG_L1)) #define LOGD(string) nitrokey::log::Log::instance()((string), (nitrokey::log::Loglevel::DEBUG_L2)) #endif -- cgit v1.2.3 From 47b2406684683bdee02cc79ae0e0d8ce9c5d3320 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 8 Dec 2017 10:56:09 +0100 Subject: Show connection success/failure on DEBUG_L1 Clarify disconnection log message Signed-off-by: Szczepan Zalega --- device.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/device.cc b/device.cc index 5a83e2e..9321d2a 100644 --- a/device.cc +++ b/device.cc @@ -69,7 +69,7 @@ bool Device::_disconnect() { LOG(std::string(__FUNCTION__) + std::string(m_model == DeviceModel::PRO ? "PRO" : "STORAGE"), Loglevel::DEBUG_L2); LOG(std::string(__FUNCTION__) + std::string(" *IN* "), Loglevel::DEBUG_L2); - LOG(std::string("Disconnection success: ") + std::to_string(mp_devhandle == nullptr), Loglevel::DEBUG_L2); + LOG(std::string("Disconnection: handle already freed: ") + std::to_string(mp_devhandle == nullptr) + " ("+m_path+")", Loglevel::DEBUG_L1); if(mp_devhandle == nullptr) return false; hid_close(mp_devhandle); @@ -99,7 +99,7 @@ bool Device::_connect() { mp_devhandle = hid_open_path(m_path.c_str()); } const bool success = mp_devhandle != nullptr; - LOG(std::string("Connection success: ") + std::to_string(success), Loglevel::DEBUG_L2); + LOG(std::string("Connection success: ") + std::to_string(success) + " ("+m_path+")", Loglevel::DEBUG_L1); return success; } -- cgit v1.2.3 From 8bede81660658a3675f4b7f68835ef3a623c696f Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 8 Dec 2017 10:59:42 +0100 Subject: Disconnect all devices before enumerating and discovering ids. Add log. Disconnection allows rediscovering devices. Signed-off-by: Szczepan Zalega --- NitrokeyManager.cc | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index e3e1730..b3c92a1 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -113,13 +113,25 @@ using nitrokey::misc::strcpyT; } std::vector NitrokeyManager::list_devices_by_cpuID(){ + //disconnect default device + disconnect(); + std::lock_guard lock(mex_dev_com_manager); + LOGD1("Disconnecting registered devices"); + for (auto & kv : connected_devices_byID){ + if (kv.second != nullptr) + kv.second->disconnect(); + } + connected_devices_byID.clear(); + LOGD1("Enumerating devices"); std::vector res; auto d = make_shared(); const auto v = d->enumerate(); + LOGD1("Discovering IDs"); for (auto & p: v){ d = make_shared(); + LOGD1( std::string("Found: ") + p ); d->set_path(p); try{ if (d->connect()){ @@ -131,13 +143,13 @@ using nitrokey::misc::strcpyT; auto id = std::to_string(sc_id) + ":" + std::to_string(sd_id); connected_devices_byID[id] = d; res.push_back(id); + LOGD1( std::string("Found: ") + p + " => " + id); } else{ - std::cout << "Could not connect to: " + p << std::endl; + LOGD1( std::string("Could not connect to: ") + p); } } catch (const DeviceCommunicationException &e){ - //ignore - std::cout << p << ": " << " Exception encountered" << std::endl; + LOGD1( std::string("Exception encountered: ") + p); } } return res; @@ -157,6 +169,7 @@ using nitrokey::misc::strcpyT; auto d = connected_devices_byID[id]; device = d; + //validate connection try{ get_status(); } -- cgit v1.2.3 From e25a83d6d704db7b5505d113c2d89811c6c2fc60 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 8 Dec 2017 11:00:18 +0100 Subject: Add tests for refreshed devices list and not refreshed in the loop Add it to CMake Signed-off-by: Szczepan Zalega --- CMakeLists.txt | 2 +- unittest/test_multiple_devices.cc | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d788cf..6037393 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -192,7 +192,7 @@ IF (COMPILE_TESTS) target_link_libraries (test_issues ${EXTRA_LIBS} nitrokey catch) add_executable (test_multiple_devices unittest/test_multiple_devices.cc) - target_link_libraries (test_multiple_devices ${EXTRA_LIBS} ${LIBNAME} catch) + target_link_libraries (test_multiple_devices ${EXTRA_LIBS} nitrokey catch) ENDIF() diff --git a/unittest/test_multiple_devices.cc b/unittest/test_multiple_devices.cc index 235a24d..65ae9ab 100644 --- a/unittest/test_multiple_devices.cc +++ b/unittest/test_multiple_devices.cc @@ -8,7 +8,6 @@ const char * RFC_SECRET = "12345678901234567890"; #include #include -#include #include using namespace nitrokey; @@ -99,13 +98,43 @@ TEST_CASE("Use API ID", "[BASIC]") { auto v = nm->list_devices_by_cpuID(); REQUIRE(v.size() > 0); - for(int i=0; i<1000; i++) { + //no refresh - should not reconnect to new devices + for (int j = 0; j < 100; j++) { + for (auto i : v) { + if (!nm->connect_with_ID(i)) continue; + int retry_count = 99; + try { + retry_count = nm->get_admin_retry_count(); + std::cout << j << " " << i << " " << to_string(retry_count) << std::endl; + } + catch (...) { + retry_count = 99; + //pass + } + } + } + std::cout << "finished" << std::endl; +} + +TEST_CASE("Use API ID refresh", "[BASIC]") { + auto nm = NitrokeyManager::instance(); + nm->set_loglevel(2); + + //refresh in each iteration - should reconnect to new devices + for(int j=0; j<100; j++) { auto v = nm->list_devices_by_cpuID(); REQUIRE(v.size() > 0); for (auto i : v) { nm->connect_with_ID(i); - auto retry_count = nm->get_admin_retry_count(); - std::cout << i << " " << to_string(retry_count) << std::endl; + int retry_count = 99; + try { + retry_count = nm->get_admin_retry_count(); + std::cout << j <<" " << i << " " << to_string(retry_count) << std::endl; + } + catch (...){ + retry_count = 99; + //pass + } } } std::cout << "finished" << std::endl; -- cgit v1.2.3 From b9e72caeed17149cd20f146e895bf66523daeff5 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 8 Dec 2017 11:47:43 +0100 Subject: Return USB path as id, when device is running long operation Signed-off-by: Szczepan Zalega --- NitrokeyManager.cc | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index b3c92a1..162ad93 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -136,11 +136,18 @@ using nitrokey::misc::strcpyT; try{ if (d->connect()){ device = d; - const auto status = get_status_storage(); - const auto sc_id = status.ActiveSmartCardID_u32; - const auto sd_id = status.ActiveSD_CardID_u32; + std::string id; + try { + const auto status = get_status_storage(); + const auto sc_id = status.ActiveSmartCardID_u32; + const auto sd_id = status.ActiveSD_CardID_u32; + id = std::to_string(sc_id) + ":" + std::to_string(sd_id); + } + catch (const LongOperationInProgressException &e) { + LOGD1(std::string("Long operation in progress, setting ID to: ") + p); + id = p; + } - auto id = std::to_string(sc_id) + ":" + std::to_string(sd_id); connected_devices_byID[id] = d; res.push_back(id); LOGD1( std::string("Found: ") + p + " => " + id); @@ -173,6 +180,9 @@ using nitrokey::misc::strcpyT; try{ get_status(); } + catch (const LongOperationInProgressException &){ + //ignore + } catch (const DeviceCommunicationException &){ d->disconnect(); connected_devices_byID[id] = nullptr; -- cgit v1.2.3 From d6ae8192be443749fcd1f593db0f9be4d039da93 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 8 Dec 2017 11:48:26 +0100 Subject: Python test running commands on all connected devices Signed-off-by: Szczepan Zalega --- unittest/test_multiple.py | 60 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 unittest/test_multiple.py diff --git a/unittest/test_multiple.py b/unittest/test_multiple.py new file mode 100644 index 0000000..7ea195e --- /dev/null +++ b/unittest/test_multiple.py @@ -0,0 +1,60 @@ +import pprint +from time import sleep + +import pytest +from collections import defaultdict + +from tqdm import tqdm + +from conftest import skip_if_device_version_lower_than +from constants import DefaultPasswords, DeviceErrorCode, bb +from misc import gs, wait + +pprint = pprint.PrettyPrinter(indent=4).pprint + + +@pytest.mark.other +@pytest.mark.info +def test_get_status_storage_multiple(C): + ids = gs(C.NK_list_devices_by_cpuID()) + print(ids) + devices_list = ids.split(b';') + # + # for s in devices_list: + # res = C.NK_connect_with_ID(s) + # assert res == 1 + # # st = gs(C.NK_get_status_storage_as_string()) + # # print(len(st)) + # C.NK_lock_device() + # + for s in devices_list: + res = C.NK_connect_with_ID(s) + assert res == 1 + v = C.NK_fill_SD_card_with_random_data(b'12345678') + # print(v) + + devices_count = len(devices_list) + assert devices_count != 0 + b = 0 + + last_b = 0 + with tqdm(total=devices_count*100) as pbar: + while b/devices_count < 100: + pbar.update(b - last_b) + + b = defaultdict (lambda: 0) + + ids = gs(C.NK_list_devices_by_cpuID()) + devices_list = ids.split(b';') + devices_count = len(devices_list) + + for s in devices_list: + res = C.NK_connect_with_ID(s) + if res != 1: continue + b[s] += C.NK_get_progress_bar_value() + print(b) + b = sum(b.values()) + print('{}: {}'.format(b, int(b/devices_count) * '=')) + sleep(5) + + -- cgit v1.2.3 From 38c3b4c58e6c6b86d6241183be1814bac2e037d2 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 16 Feb 2018 15:17:47 +0100 Subject: Log current device ID Allow logger to set global prefix Used to indicate current device Store USB path when used to connection as well Signed-off-by: Szczepan Zalega --- NitrokeyManager.cc | 16 +++++++++++++++- device.cc | 2 +- include/NitrokeyManager.h | 5 +++++ include/log.h | 5 +++++ log.cc | 17 +++++++++++++++-- 5 files changed, 41 insertions(+), 4 deletions(-) diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index 162ad93..f2518b1 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -171,10 +171,14 @@ using nitrokey::misc::strcpyT; std::lock_guard lock(mex_dev_com_manager); auto position = connected_devices_byID.find(id); - if (position == connected_devices_byID.end()) return false; + if (position == connected_devices_byID.end()) { + LOGD1(std::string("Could not find device ")+id); + return false; + } auto d = connected_devices_byID[id]; device = d; + current_device_id = id; //validate connection try{ @@ -185,10 +189,13 @@ using nitrokey::misc::strcpyT; } catch (const DeviceCommunicationException &){ d->disconnect(); + current_device_id = ""; connected_devices_byID[id] = nullptr; connected_devices_byID.erase(position); return false; } + nitrokey::log::Log::setPrefix(id); + LOGD1("Device successfully changed"); return true; } @@ -221,6 +228,9 @@ using nitrokey::misc::strcpyT; } device = p; //previous device will be disconnected automatically + current_device_id = path; + nitrokey::log::Log::setPrefix(path); + LOGD1("Device successfully changed"); return true; } @@ -1105,5 +1115,9 @@ using nitrokey::misc::strcpyT; return data.data().SD_Card_Size_u8; } + const string NitrokeyManager::get_current_device_id() const { + return current_device_id; + } + } diff --git a/device.cc b/device.cc index 9321d2a..da5345b 100644 --- a/device.cc +++ b/device.cc @@ -248,7 +248,7 @@ Stick10::Stick10(): Stick20::Stick20(): - Device(0x20a0, 0x4109, DeviceModel::STORAGE, 40ms, 25, 40ms) + Device(0x20a0, 0x4109, DeviceModel::STORAGE, 40ms, 55, 40ms) { setDefaultDelay(); } diff --git a/include/NitrokeyManager.h b/include/NitrokeyManager.h index 7ce432f..5a76616 100644 --- a/include/NitrokeyManager.h +++ b/include/NitrokeyManager.h @@ -203,6 +203,11 @@ char * strndup(const char* str, size_t maxlen); static shared_ptr _instance; std::shared_ptr device; + std::string current_device_id; + public: + const string get_current_device_id() const; + + private: std::unordered_map > connected_devices; std::unordered_map > connected_devices_byID; diff --git a/include/log.h b/include/log.h index 89eda56..b969b81 100644 --- a/include/log.h +++ b/include/log.h @@ -86,6 +86,11 @@ namespace nitrokey { private: LogHandler *mp_loghandler; Loglevel m_loglevel; + static std::string prefix; + public: + static void setPrefix(std::string prefix = std::string()); + + private: static Log *mp_instance; }; diff --git a/log.cc b/log.cc index 263ddd7..e66feb0 100644 --- a/log.cc +++ b/log.cc @@ -26,6 +26,7 @@ #include "log.h" #include +#include namespace nitrokey { namespace log { @@ -33,6 +34,9 @@ namespace nitrokey { Log *Log::mp_instance = nullptr; StdlogHandler stdlog_handler; + std::string Log::prefix = ""; + + std::string LogHandler::loglevel_to_str(Loglevel lvl) { switch (lvl) { case Loglevel::DEBUG_L1: @@ -52,8 +56,17 @@ namespace nitrokey { } void Log::operator()(const std::string &logstr, Loglevel lvl) { - if (mp_loghandler != nullptr) - if ((int) lvl <= (int) m_loglevel) mp_loghandler->print(logstr, lvl); + if (mp_loghandler != nullptr){ + if ((int) lvl <= (int) m_loglevel) mp_loghandler->print(prefix+logstr, lvl); + } + } + + void Log::setPrefix(const string prefix) { + if (!prefix.empty()){ + Log::prefix = "["+prefix+"]"; + } else { + Log::prefix = ""; + } } void StdlogHandler::print(const std::string &str, Loglevel lvl) { -- cgit v1.2.3 From a1e081303dc7f7329e5452dad7e6cedf7efe3699 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Mon, 26 Feb 2018 15:37:13 +0100 Subject: Add to device's ID USB path. Convert ID to hex. Signed-off-by: Szczepan Zalega --- NitrokeyManager.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index f2518b1..fa69ac6 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -113,6 +113,7 @@ using nitrokey::misc::strcpyT; } std::vector NitrokeyManager::list_devices_by_cpuID(){ + using misc::toHex; //disconnect default device disconnect(); @@ -139,9 +140,9 @@ using nitrokey::misc::strcpyT; std::string id; try { const auto status = get_status_storage(); - const auto sc_id = status.ActiveSmartCardID_u32; - const auto sd_id = status.ActiveSD_CardID_u32; - id = std::to_string(sc_id) + ":" + std::to_string(sd_id); + const auto sc_id = toHex(status.ActiveSmartCardID_u32); + const auto sd_id = toHex(status.ActiveSD_CardID_u32); + id = sc_id + ":" + sd_id + "_p_" + p; } catch (const LongOperationInProgressException &e) { LOGD1(std::string("Long operation in progress, setting ID to: ") + p); -- cgit v1.2.3 From 2b752acc507d60047576876c9ada4596442511b6 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Wed, 28 Feb 2018 20:28:33 +0100 Subject: Add test's scenario descriptions Signed-off-by: Szczepan Zalega --- unittest/test_multiple_devices.cc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/unittest/test_multiple_devices.cc b/unittest/test_multiple_devices.cc index 65ae9ab..f497908 100644 --- a/unittest/test_multiple_devices.cc +++ b/unittest/test_multiple_devices.cc @@ -99,6 +99,12 @@ TEST_CASE("Use API ID", "[BASIC]") { REQUIRE(v.size() > 0); //no refresh - should not reconnect to new devices + // Scenario: + // 1. Run test + // 2. Remove one of the devices and reinsert it after a while + // 3. Device should not be reconnected and test should not crash + // 4. Remove all devices - test should continue + for (int j = 0; j < 100; j++) { for (auto i : v) { if (!nm->connect_with_ID(i)) continue; @@ -121,6 +127,11 @@ TEST_CASE("Use API ID refresh", "[BASIC]") { nm->set_loglevel(2); //refresh in each iteration - should reconnect to new devices + // Scenario: + // 1. Run test + // 2. Remove one of the devices and reinsert it after a while + // 3. Device should be reconnected + for(int j=0; j<100; j++) { auto v = nm->list_devices_by_cpuID(); REQUIRE(v.size() > 0); -- cgit v1.2.3 From 01ed5b4302f21a3d77300c2e6f34344fec36d40c Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Thu, 1 Mar 2018 11:26:10 +0100 Subject: Clean headers Signed-off-by: Szczepan Zalega --- include/log.h | 2 -- log.cc | 6 ++---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/include/log.h b/include/log.h index b969b81..2a64bef 100644 --- a/include/log.h +++ b/include/log.h @@ -23,8 +23,6 @@ #define LOG_H #include -#include - #include namespace nitrokey { diff --git a/log.cc b/log.cc index e66feb0..06acee7 100644 --- a/log.cc +++ b/log.cc @@ -19,14 +19,12 @@ * SPDX-License-Identifier: LGPL-3.0 */ +#include "log.h" #include -#include #include #include -#include "log.h" #include -#include namespace nitrokey { namespace log { @@ -61,7 +59,7 @@ namespace nitrokey { } } - void Log::setPrefix(const string prefix) { + void Log::setPrefix(const std::string prefix) { if (!prefix.empty()){ Log::prefix = "["+prefix+"]"; } else { -- cgit v1.2.3 From 38c9cc878fb830fa709c5e56eda93f3310fea6e5 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Thu, 1 Mar 2018 11:26:48 +0100 Subject: Add documentation for C API / multiple devices Signed-off-by: Szczepan Zalega --- NK_C_API.h | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/NK_C_API.h b/NK_C_API.h index 5933c67..3778ad0 100644 --- a/NK_C_API.h +++ b/NK_C_API.h @@ -269,6 +269,7 @@ extern "C" { NK_C_API int NK_totp_set_time(uint64_t time); NK_C_API int NK_totp_get_time(); + //passwords /** * Change administrator PIN @@ -568,8 +569,32 @@ extern "C" { */ NK_C_API int NK_get_progress_bar_value(); - +/** + * Returns a list of connected devices' id's, delimited by ';' character. Empty string is returned on no device found. + * Each ID could consist of: + * 1. SC_id:SD_id_p_path (about 40 bytes) + * 2. path (about 10 bytes) + * where 'path' is USB path (bus:num), 'SC_id' is smartcard ID, 'SD_id' is storage card ID and + * '_p_' and ':' are field delimiters. + * Case 2 (USB path only) is used, when the device cannot be asked about its status data (e.g. during a long operation, + * like clearing SD card. + * Internally connects to all available devices and creates a map between ids and connection objects. + * Side effects: changes active device to last detected Storage device. + * Storage only + * @example Example of returned data: '00005d19:dacc2cb4_p_0001:0010:02;000037c7:4cf12445_p_0001:000f:02;0001:000c:02' + * @return string delimited id's of connected devices + */ NK_C_API const char* NK_list_devices_by_cpuID(); + + +/** + * Connects to the device with given ID. ID's list could be created with NK_list_devices_by_cpuID. + * Requires calling to NK_list_devices_by_cpuID first. Connecting to arbitrary ID/USB path is not handled. + * On connection requests status from device and disconnects it / removes from map on connection failure. + * Storage only + * @param id Target device ID (example: '00005d19:dacc2cb4_p_0001:0010:02') + * @return 1 on successful connection, 0 otherwise + */ NK_C_API int NK_connect_with_ID(const char* id); -- cgit v1.2.3 From e6fc0cce3937b458179bed8648825f9f047a68d8 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Thu, 1 Mar 2018 11:27:50 +0100 Subject: Increase buffer size for listing connected devices IDs Signed-off-by: Szczepan Zalega --- NK_C_API.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NK_C_API.cc b/NK_C_API.cc index 59247ba..66bbcf9 100644 --- a/NK_C_API.cc +++ b/NK_C_API.cc @@ -616,8 +616,8 @@ extern "C" { for (const auto a : v){ res += a+";"; } - if (res.size()>0) res.pop_back(); // remove last delimeter char - return strndup(res.c_str(), 4096); + if (res.size()>0) res.pop_back(); // remove last delimiter char + return strndup(res.c_str(), 8192); //this buffer size sets limit to over 200 devices ID's }); } -- cgit v1.2.3 From f5da7b4cbd508faef38fcdfff659fab5c4649838 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Thu, 1 Mar 2018 11:29:57 +0100 Subject: Minor cleanup Signed-off-by: Szczepan Zalega --- NitrokeyManager.cc | 12 ++++-------- include/NitrokeyManager.h | 7 +++++++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index fa69ac6..e5f10df 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -142,7 +142,8 @@ using nitrokey::misc::strcpyT; const auto status = get_status_storage(); const auto sc_id = toHex(status.ActiveSmartCardID_u32); const auto sd_id = toHex(status.ActiveSD_CardID_u32); - id = sc_id + ":" + sd_id + "_p_" + p; + id += sc_id + ":" + sd_id; + id += "_p_" + p; } catch (const LongOperationInProgressException &e) { LOGD1(std::string("Long operation in progress, setting ID to: ") + p); @@ -162,18 +163,13 @@ using nitrokey::misc::strcpyT; } return res; } -/** - * Connect to the device using unique smartcard:datacard id. - * Needs list_device_by_cpuID run first - * @param id - * @return - */ + bool NitrokeyManager::connect_with_ID(const std::string id) { std::lock_guard lock(mex_dev_com_manager); auto position = connected_devices_byID.find(id); if (position == connected_devices_byID.end()) { - LOGD1(std::string("Could not find device ")+id); + LOGD1(std::string("Could not find device ")+id + ". Refresh devices list with list_devices_by_cpuID()."); return false; } diff --git a/include/NitrokeyManager.h b/include/NitrokeyManager.h index 5a76616..ca58d24 100644 --- a/include/NitrokeyManager.h +++ b/include/NitrokeyManager.h @@ -70,6 +70,13 @@ char * strndup(const char* str, size_t maxlen); bool erase_hotp_slot(uint8_t slot_number, const char *temporary_password); std::vector list_devices(); std::vector list_devices_by_cpuID(); + + /** + * Connect to the device using unique smartcard:datacard id. + * Needs list_device_by_cpuID() run first + * @param id Current ID of the target device + * @return true on success, false on failure + */ bool connect_with_ID(const std::string id); bool connect_with_path (std::string path); bool connect(const char *device_model); -- cgit v1.2.3 From c3d615b659b608f3a1d624f6fc78c303efbe1f8e Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Thu, 1 Mar 2018 19:41:35 +0100 Subject: Correct firmware version getting in Pro tests Done for Pro devices Signed-off-by: Szczepan Zalega --- unittest/misc.py | 7 ++++--- unittest/test_pro.py | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/unittest/misc.py b/unittest/misc.py index a41f2b2..8be915d 100644 --- a/unittest/misc.py +++ b/unittest/misc.py @@ -43,7 +43,7 @@ def cast_pointer_to_tuple(obj, typen, len): def get_devices_firmware_version(C): - firmware = C.NK_get_major_firmware_version() + firmware = C.NK_get_minor_firmware_version() return firmware @@ -54,7 +54,7 @@ def is_pro_rtm_07(C): def is_pro_rtm_08(C): firmware = get_devices_firmware_version(C) - return firmware == 8 + return firmware in [8,9] def is_storage(C): @@ -62,7 +62,8 @@ def is_storage(C): exact firmware storage is sent by other function """ # TODO identify connected device directly - return not is_pro_rtm_08(C) and not is_pro_rtm_07(C) + firmware = get_devices_firmware_version(C) + return firmware >= 45 def is_long_OTP_secret_handled(C): diff --git a/unittest/test_pro.py b/unittest/test_pro.py index 12a34e9..53588f6 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -164,9 +164,9 @@ def test_password_safe_slot_status(C): @pytest.mark.aes def test_issue_device_locks_on_second_key_generation_in_sequence(C): - if is_pro_rtm_07(C) or is_pro_rtm_08(C): - pytest.skip("issue to register: device locks up " - "after below commands sequence (reinsertion fixes), skipping for now") +# if is_pro_rtm_07(C) or is_pro_rtm_08(C): + pytest.skip("issue to register: device locks up " + "after below commands sequence (reinsertion fixes), skipping for now") assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK -- cgit v1.2.3