diff options
| -rw-r--r-- | .travis.yml | 12 | ||||
| -rw-r--r-- | CMakeLists.txt | 2 | ||||
| -rw-r--r-- | NK_C_API.cc | 95 | ||||
| -rw-r--r-- | NK_C_API.h | 84 | ||||
| -rw-r--r-- | NitrokeyManager.cc | 41 | ||||
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | device.cc | 66 | ||||
| -rw-r--r-- | libnitrokey/NitrokeyManager.h | 6 | ||||
| -rw-r--r-- | libnitrokey/device.h | 57 | ||||
| -rw-r--r-- | libnitrokey/misc.h | 25 | ||||
| -rw-r--r-- | unittest/conftest.py | 72 | ||||
| -rw-r--r-- | unittest/requirements.txt | 3 | ||||
| -rw-r--r-- | unittest/test_multiple.py | 29 | ||||
| -rw-r--r-- | unittest/test_multiple_devices.cc | 81 | ||||
| -rw-r--r-- | unittest/test_offline.py | 39 | ||||
| -rw-r--r-- | unittest/test_storage.py | 30 | 
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([&]() { @@ -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); @@ -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). @@ -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) | 
