Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package libnitrokey for openSUSE:Factory checked in at 2022-05-06 18:59:47 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/libnitrokey (Old) and /work/SRC/openSUSE:Factory/.libnitrokey.new.1538 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "libnitrokey" Fri May 6 18:59:47 2022 rev:2 rq:975343 version:3.7 Changes: -------- --- /work/SRC/openSUSE:Factory/libnitrokey/libnitrokey.changes 2022-04-25 23:35:27.622410827 +0200 +++ /work/SRC/openSUSE:Factory/.libnitrokey.new.1538/libnitrokey.changes 2022-05-06 19:00:09.813410604 +0200 @@ -1,0 +2,9 @@ +Tue May 3 05:57:51 UTC 2022 - Martin Sirringhaus <martin.sirringh...@suse.com> + +- Update to 3.7 + * Nitrokey Pro v0.14 support; + * Udev rules update; + * Stability fixes. +- drop udev-rules-add-nitrokey3.patch which is now upstream + +------------------------------------------------------------------- Old: ---- libnitrokey-v3.6.tar.gz libnitrokey-v3.6.tar.gz.sig udev-rules-add-nitrokey3.patch New: ---- libnitrokey-v3.7.tar.gz libnitrokey-v3.7.tar.gz.sig ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ libnitrokey.spec ++++++ --- /var/tmp/diff_new_pack.TK50il/_old 2022-05-06 19:00:11.373412307 +0200 +++ /var/tmp/diff_new_pack.TK50il/_new 2022-05-06 19:00:11.381412317 +0200 @@ -1,7 +1,7 @@ # # spec file for package libnitrokey # -# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2022 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -12,7 +12,7 @@ # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. -# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# Please submit bugfixes or comments via https://bugs.opensuse.org/ # @@ -22,7 +22,7 @@ %define lib_name %{name}3 Name: libnitrokey -Version: 3.6 +Version: 3.7 Release: 0 Summary: Communicate with Nitrokey stick devices in a clean and easy manner License: LGPL-3.0-only @@ -30,7 +30,6 @@ URL: https://github.com/Nitrokey/libnitrokey Source: https://github.com/Nitrokey/libnitrokey/releases/download/v%{version}/libnitrokey-v%{version}.tar.gz Source1: https://github.com/Nitrokey/libnitrokey/releases/download/v%{version}/libnitrokey-v%{version}.tar.gz.sig -Patch1: udev-rules-add-nitrokey3.patch BuildRequires: cmake BuildRequires: gcc%{?force_gcc_version}-c++ >= 5 %if 0%{?force_gcc_version} @@ -38,9 +37,9 @@ %endif BuildRequires: pkgconfig BuildRequires: python3-devel -BuildRequires: pkgconfig(udev) BuildRequires: pkgconfig(hidapi-libusb) BuildRequires: pkgconfig(libusb-1.0) +BuildRequires: pkgconfig(udev) %description Libnitrokey is a project to communicate with Nitrokey Pro and Storage devices @@ -57,11 +56,11 @@ This package holds the shared library. - %package udev Summary: udev rules for libnitrokey Group: Development/Libraries/C and C++ BuildArch: noarch + %description udev Libnitrokey is a project to communicate with Nitrokey Pro and Storage devices in a clean and easy manner. @@ -81,7 +80,6 @@ %prep %setup -q -n %{name}-v%{version} -%patch1 -p1 %build %if 0%{?force_gcc_version} ++++++ libnitrokey-v3.6.tar.gz -> libnitrokey-v3.7.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/.gitignore new/libnitrokey-v3.7/.gitignore --- old/libnitrokey-v3.6/.gitignore 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/.gitignore 2022-04-27 14:35:24.000000000 +0200 @@ -1,6 +1,7 @@ *.sw* *.log *.o +build unittest/build/ unittest/.pytest_cache/ *.pyc diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/.gitlab-ci.yml new/libnitrokey-v3.7/.gitlab-ci.yml --- old/libnitrokey-v3.6/.gitlab-ci.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/libnitrokey-v3.7/.gitlab-ci.yml 2022-04-27 14:35:24.000000000 +0200 @@ -0,0 +1,75 @@ +include: 'https://raw.githubusercontent.com/Nitrokey/common-ci-jobs/master/common_jobs.yml' + +stages: + - pull-github + - fetch + - build + - deploy + +variables: + #Repo for shared scripts (pull.sh release.sh, nightly_upload.sh): + GIT_STRATEGY: clone #This seems to have no effect also set in webinterface + GIT_DEPTH: 0 #This seems to have no effect also set in webinterface + GIT_SUBMODULE_STRATEGY: recursive #This seems to have no effect also set in webinterfac + SCRIPTS_REPO: g...@git.dotplex.com:nitrokey/gitlab-ci.git + REPO_USER: nitrokey + REPO_NAME: libnitrokey + MAIN_BRANCH: master + COMMON_PULL: "true" + COMMON_UPLOAD_NIGHTLY: "true" + COMMON_GITHUB_RELEASE: "true" + COMMON_UPLOAD_FILES: "false" + +fetch-and-package: + image: registry.git.dotplex.com/nitrokey/libnitrokey/libnitrokey-bionic-gcc8:latest + rules: + - if: '$CI_PIPELINE_SOURCE == "push"' + - if: '$CI_PIPELINE_SOURCE == "schedule"' + - if: '$CI_PIPELINE_SOURCE == "web"' + + tags: + - docker + stage: fetch + script: + - ci-script/package.sh + after_script: + - wget $icon_server/checkmark/$CI_COMMIT_REF_NAME/$CI_COMMIT_SHA/$CI_JOB_NAME/$CI_JOB_STATUS/${CI_JOB_URL#*/*/*/} + artifacts: + paths: + - artifacts + - libnitrokey-source-metadata + expire_in: 2 weeks + +.build: + rules: + - if: '$CI_PIPELINE_SOURCE == "push"' + - if: '$CI_PIPELINE_SOURCE == "schedule"' + - if: '$CI_PIPELINE_SOURCE == "web"' + tags: + - docker + stage: build + script: + - ci-script/build.sh + after_script: + - wget $icon_server/checkmark/$CI_COMMIT_REF_NAME/$CI_COMMIT_SHA/$CI_JOB_NAME/$CI_JOB_STATUS/${CI_JOB_URL#*/*/*/} + +build-bionic-gcc8: + extends: .build + image: registry.git.dotplex.com/nitrokey/libnitrokey/libnitrokey-bionic-gcc8:latest + +build-bionic-gcc7: + extends: .build + image: registry.git.dotplex.com/nitrokey/libnitrokey/libnitrokey-bionic-gcc7:latest + +build-bionic-gcc6: + extends: .build + image: registry.git.dotplex.com/nitrokey/libnitrokey/libnitrokey-bionic-gcc6:latest + +build-bionic-gcc5: + extends: .build + image: registry.git.dotplex.com/nitrokey/libnitrokey/libnitrokey-bionic-gcc5:latest + +build-bionic-llvm7: + extends: .build + image: registry.git.dotplex.com/nitrokey/libnitrokey/libnitrokey-bionic-llvm7:latest + \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/CMakeLists.txt new/libnitrokey-v3.7/CMakeLists.txt --- old/libnitrokey-v3.6/CMakeLists.txt 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/CMakeLists.txt 2022-04-27 14:35:24.000000000 +0200 @@ -21,7 +21,7 @@ ENDIF() ENDIF() -project(libnitrokey LANGUAGES C CXX VERSION 3.6.0) +project(libnitrokey LANGUAGES C CXX VERSION 3.7.0) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/NK_C_API.cc new/libnitrokey-v3.7/NK_C_API.cc --- old/libnitrokey-v3.6/NK_C_API.cc 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/NK_C_API.cc 2022-04-27 14:35:24.000000000 +0200 @@ -426,6 +426,14 @@ m->set_loglevel(level); } + NK_C_API void NK_set_log_function(NK_log_function fn) { + auto m = NitrokeyManager::instance(); + std::function<void(const std::string&, Loglevel)> log_function = [fn](auto s, auto lvl) { + fn(static_cast<int>(lvl), s.c_str()); + }; + m->set_log_function_raw(log_function); + } + NK_C_API unsigned int NK_get_major_library_version() { return get_major_library_version(); } @@ -929,6 +937,20 @@ }); } + NK_C_API int NK_get_random(const uint8_t len, struct GetRandom_t *out){ + if (out == nullptr) return -1; + auto m = NitrokeyManager::instance(); + auto result = get_with_status([&]() { + return m->get_random_pro(len); + }, stick10::GetRandom::ResponsePayload()); + auto error_code = std::get<0>(result); + if (error_code != 0) { + return error_code; + } + auto data = std::get<1>(result); + memmove(out, reinterpret_cast<void *>(&data), sizeof(struct GetRandom_t)); + return 0; + } NK_C_API int NK_read_HOTP_slot(const uint8_t slot_num, struct ReadSlot_t* out){ if (out == nullptr) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/NK_C_API.h new/libnitrokey-v3.7/NK_C_API.h --- old/libnitrokey-v3.6/NK_C_API.h 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/NK_C_API.h 2022-04-27 14:35:24.000000000 +0200 @@ -41,7 +41,7 @@ * \mainpage * * **libnitrokey** provides access to Nitrokey Pro and Nitrokey Storage devices. - * This documentation describes libnitrokey???s C API. For a list of the + * This documentation describes libnitrokey's C API. For a list of the * available functions, see the NK_C_API.h file. * * \section getting_started Example @@ -331,6 +331,21 @@ NK_C_API void NK_set_debug_level(const int level); /** + * Callback function for NK_set_log_function. The first argument is + * the log level (0 = Error, 1 = Warn, 2 = Info, 3 = DebugL1, + * 4 = Debug, 5 = DebugL2) and the second argument is the log message. + */ + NK_C_API typedef void (*NK_log_function)(int, const char*); + + /** + * Set a custom log function. + * + * The log function is called for every log message that matches the + * log level settings (see NK_set_debug and NK_set_debug_level). + */ + NK_C_API void NK_set_log_function(NK_log_function fn); + + /** * Get the major library version, e. g. the 3 in v3.2. * @return the major library version */ @@ -974,7 +989,7 @@ /** * 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 + * 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 @@ -1080,6 +1095,13 @@ uint64_t slot_counter; }; +struct GetRandom_t { + uint8_t op_success; + uint8_t size_effective; + uint8_t data[51]; +}; + +NK_C_API int NK_get_random(const uint8_t len, struct GetRandom_t *out); NK_C_API int NK_read_HOTP_slot(const uint8_t slot_num, struct ReadSlot_t* out); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/NitrokeyManager.cc new/libnitrokey-v3.7/NitrokeyManager.cc --- old/libnitrokey-v3.6/NitrokeyManager.cc 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/NitrokeyManager.cc 2022-04-27 14:35:24.000000000 +0200 @@ -217,44 +217,44 @@ } } - auto vendor_id = NITROKEY_VID; - auto info_ptr = hid_enumerate(vendor_id, 0); - if (!info_ptr) { - vendor_id = PURISM_VID; - info_ptr = hid_enumerate(vendor_id, 0); - } - auto first_info_ptr = info_ptr; - if (!info_ptr) - return false; + auto vendor_ids = { NITROKEY_VID, PURISM_VID }; + for (auto vendor_id : vendor_ids) { + auto info_ptr = hid_enumerate(vendor_id, 0); + if (!info_ptr) { + continue; + } + auto first_info_ptr = info_ptr; - misc::Option<DeviceModel> model; - while (info_ptr && !model.has_value()) { - if (path == std::string(info_ptr->path)) { - model = product_id_to_model(info_ptr->vendor_id, info_ptr->product_id); - } - info_ptr = info_ptr->next; - } - hid_free_enumeration(first_info_ptr); + misc::Option<DeviceModel> model; + while (info_ptr && !model.has_value()) { + if (path == std::string(info_ptr->path)) { + model = product_id_to_model(info_ptr->vendor_id, info_ptr->product_id); + } + info_ptr = info_ptr->next; + } + hid_free_enumeration(first_info_ptr); - if (!model.has_value()) - return false; + if (!model.has_value()) + continue; - auto p = Device::create(model.value()); - if (!p) - return false; - p->set_path(path); + auto p = Device::create(model.value()); + if (!p) + continue; + p->set_path(path); - if(!p->connect()) return false; + if(!p->connect()) continue; - if(cache_connections){ - connected_devices [path] = p; - } + if(cache_connections){ + connected_devices [path] = p; + } - device = p; //previous device will be disconnected automatically - current_device_id = path; - nitrokey::log::Log::setPrefix(path); - LOGD1("Device successfully changed"); - return true; + device = p; //previous device will be disconnected automatically + current_device_id = path; + nitrokey::log::Log::setPrefix(path); + LOGD1("Device successfully changed"); + return true; + } + return false; } bool NitrokeyManager::connect() { @@ -273,10 +273,17 @@ void NitrokeyManager::set_log_function(std::function<void(std::string)> log_function){ + // FIXME use move and pass to log handler, instead of keeping here static variable static nitrokey::log::FunctionalLogHandler handler(log_function); nitrokey::log::Log::instance().set_handler(&handler); } + void NitrokeyManager::set_log_function_raw(std::function<void(const std::string&, Loglevel)> log_function) { + // FIXME use move and pass to log handler, instead of keeping here static variable + static nitrokey::log::RawFunctionalLogHandler handler(log_function); + nitrokey::log::Log::instance().set_handler(&handler); + } + bool NitrokeyManager::set_default_commands_delay(int delay){ if (delay < 20){ LOG("Delay set too low: " + to_string(delay), Loglevel::WARNING); @@ -571,6 +578,7 @@ void NitrokeyManager::write_HOTP_slot_authorize(uint8_t slot_number, const char *slot_name, const char *secret, uint64_t hotp_counter, bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, const char *temporary_password) { + if (device == nullptr) { throw DeviceNotConnected("device not connected"); } auto payload = get_payload<WriteToHOTPSlot>(); payload.slot_number = slot_number; auto secret_bin = misc::hex_string_to_byte(secret); @@ -739,6 +747,7 @@ template <typename ProCommand, PasswordKind StoKind> void NitrokeyManager::change_PIN_general(const char *current_PIN, const char *new_PIN) { + if (device == nullptr) { throw DeviceNotConnected("device not connected"); } switch (device->get_device_model()){ case DeviceModel::LIBREM: case DeviceModel::PRO: @@ -786,6 +795,7 @@ } uint8_t NitrokeyManager::get_user_retry_count() { + if (device == nullptr) { throw DeviceNotConnected("device not connected"); } if(device->get_device_model() == DeviceModel::STORAGE){ stick20::GetDeviceStatus::CommandTransaction::run(device); } @@ -794,6 +804,7 @@ } uint8_t NitrokeyManager::get_admin_retry_count() { + if (device == nullptr) { throw DeviceNotConnected("device not connected"); } if(device->get_device_model() == DeviceModel::STORAGE){ stick20::GetDeviceStatus::CommandTransaction::run(device); } @@ -861,6 +872,7 @@ } void NitrokeyManager::build_aes_key(const char *admin_password) { + if (device == nullptr) { throw DeviceNotConnected("device not connected"); } switch (device->get_device_model()) { case DeviceModel::LIBREM: case DeviceModel::PRO: { @@ -886,6 +898,7 @@ } void NitrokeyManager::unlock_user_password(const char *admin_password, const char *new_user_password) { + if (device == nullptr) { throw DeviceNotConnected("device not connected"); } switch (device->get_device_model()){ case DeviceModel::LIBREM: case DeviceModel::PRO: { @@ -934,6 +947,7 @@ } bool NitrokeyManager::is_authorization_command_supported(){ + if (device == nullptr) { throw DeviceNotConnected("device not connected"); } //authorization command is supported for versions equal or below: auto m = std::unordered_map<DeviceModel , int, EnumClassHash>({ {DeviceModel::PRO, 7}, @@ -944,6 +958,7 @@ } bool NitrokeyManager::is_320_OTP_secret_supported(){ + if (device == nullptr) { throw DeviceNotConnected("device not connected"); } // 320 bit OTP secret is supported by version bigger or equal to: auto m = std::unordered_map<DeviceModel , int, EnumClassHash>({ {DeviceModel::PRO, 8}, @@ -971,6 +986,7 @@ } uint8_t NitrokeyManager::get_minor_firmware_version(){ + if (device == nullptr) { throw DeviceNotConnected("device not connected"); } switch(device->get_device_model()){ case DeviceModel::LIBREM: case DeviceModel::PRO:{ @@ -988,6 +1004,7 @@ return 0; } uint8_t NitrokeyManager::get_major_firmware_version(){ + if (device == nullptr) { throw DeviceNotConnected("device not connected"); } switch(device->get_device_model()){ case DeviceModel::LIBREM: case DeviceModel::PRO:{ @@ -1163,6 +1180,7 @@ * @return ReadSlot structure */ stick10::ReadSlot::ResponsePayload NitrokeyManager::get_OTP_slot_data(const uint8_t slot_number) { + if (device == nullptr) { throw DeviceNotConnected("device not connected"); } auto p = get_payload<stick10::ReadSlot>(); p.slot_number = slot_number; p.data_format = stick10::ReadSlot::CounterFormat::BINARY; // ignored for devices other than Storage v0.54+ @@ -1222,6 +1240,13 @@ FirmwareUpdate::CommandTransaction::run(device, p); } + GetRandom::ResponsePayload NitrokeyManager::get_random_pro(uint8_t size_requested) { + auto p = get_payload<GetRandom>(); + p.size_requested = size_requested; + auto data = GetRandom::CommandTransaction::run(device, p); + return data.data(); + } + void NitrokeyManager::change_firmware_update_password_pro(const char *firmware_pin_current, const char *firmware_pin_new) { auto p = get_payload<FirmwarePasswordChange>(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/README.md new/libnitrokey-v3.7/README.md --- old/libnitrokey-v3.6/README.md 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/README.md 2022-04-27 14:35:24.000000000 +0200 @@ -1,5 +1,5 @@ [](https://travis-ci.org/Nitrokey/libnitrokey) -[](https://waffle.io/Nitrokey/libnitrokey) +[](https://app.fossa.com/projects/git%2Bgithub.com%2FNitrokey%2Flibnitrokey?ref=badge_shield) # 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). @@ -8,7 +8,7 @@ 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). -libnitrokey is developed and tested with the latest compilers: g++ 6.2, clang 3.8. We use Travis CI to test builds also on g++ 5.4 and under OSX compilers starting up from xcode 8.2 environment. +libnitrokey is developed and tested with a variety of compilers, starting from g++ 6.2 and clang 3.8. We use Travis CI to test builds also on g++ 5.4 and under OSX compilers starting up from xcode 9 environment. ## Getting sources This repository uses `git submodules`. @@ -93,6 +93,11 @@ ``` ## Python2 Just import it, read the C API header and it is done! You have access to the library. Here is an example (in Python 2) printing HOTP code for Pro or Storage device, assuming it is run in root directory [(full example)](python_bindings_example.py): + +<details> + <summary>Code snippet (click to show)</summary> + + ```python #!/usr/bin/env python2 import cffi @@ -159,11 +164,19 @@ print(hotp_slot_code) libnitrokey.NK_logout() # disconnect device ``` + +</details> + In case no devices are connected, a friendly message will be printed. All available functions for C and Python are listed in [NK_C_API.h](NK_C_API.h). Please check `Documentation` section below. ## Python3 Just import it, read the C API header and it is done! You have access to the library. Here is an example (in Python 3) printing HOTP code for Pro or Storage device, assuming it is run in root directory [(full example)](python3_bindings_example.py): + +<details> + <summary>Code snippet (click to show)</summary> + + ```python #!/usr/bin/env python3 import cffi @@ -242,6 +255,8 @@ libnitrokey.NK_logout() # disconnect device ``` +</details> + In case no devices are connected, a friendly message will be printed. All available functions for C and Python are listed in [NK_C_API.h](NK_C_API.h). Please check `Documentation` section below. @@ -280,6 +295,10 @@ ## C++ tests There are also some unit tests implemented in C++, placed in unittest directory. The only user-data safe online test set here is [test_safe.cpp](https://github.com/Nitrokey/libnitrokey/blob/master/unittest/test_safe.cpp), which tries to connect to the device, and collect its status data. Example run for Storage: + +<details> + <summary>Log (click to show)</summary> + ```text # Storage device inserted, firmware version v0.53 $ ./test_safe @@ -340,6 +359,10 @@ =============================================================================== All tests passed (18 assertions in 6 test cases) ``` + +</details> + + Test's execution configuration and verbosity could be manipulated - please see `./test_safe --help` for details. The other tests sets are not written as extensively as Python tests and are rather more a C++ low level interface check used during the library development, using either low-level components, C API from `NK_C_API.cc`, or C++ API from `NitrokeyManager.cc`. Some of them are: [test_HOTP.cc](https://github.com/Nitrokey/libnitrokey/blob/master/unittest/test_HOTP.cc), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/ci-script/build.sh new/libnitrokey-v3.7/ci-script/build.sh --- old/libnitrokey-v3.6/ci-script/build.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/libnitrokey-v3.7/ci-script/build.sh 2022-04-27 14:35:24.000000000 +0200 @@ -0,0 +1,31 @@ +#!/bin/bash +set -exuo pipefail +export + +. ./libnitrokey-source-metadata/metadata +tar xf artifacts/${LIBNITROKEY_BUILD_OUTNAME}.tar.gz + + +pushd ${LIBNITROKEY_BUILD_OUTNAME} +pip3 install --user -r unittest/requirements.txt + +## This is quite sketchy but will work for now - we're using a tarball prepared by git archive, so we can't pull submodules. +## Instead, we download the Catch2 header manually. The version is hardcoded, which is bad. +## TODO: figure out a better way to handle that +## One possibility is to install Catch2 system-wide on builder images. +mkdir -p unittest/Catch/single_include/catch2 +curl -L -o unittest/Catch/single_include/catch2/catch.hpp https://github.com/catchorg/Catch2/releases/download/v2.3.0/catch.hpp + +mkdir build +mkdir install + +pushd build +cmake .. -DERROR_ON_WARNING=OFF -DCOMPILE_TESTS=ON +make -j2 +ctest -VV +make install DESTDIR=../install +popd + +pushd unittest +python3 -m pytest -sv test_offline.py +popd diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/ci-script/package.sh new/libnitrokey-v3.7/ci-script/package.sh --- old/libnitrokey-v3.6/ci-script/package.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/libnitrokey-v3.7/ci-script/package.sh 2022-04-27 14:35:24.000000000 +0200 @@ -0,0 +1,42 @@ +#!/bin/bash +set -exuo pipefail +export +mkdir -p artifacts +OUTDIR="$(realpath artifacts)" + +BASENAME="libnitrokey" +#pushd libnitrokey + +VERSION="$(git describe --abbrev=0)" +BUILD="${VERSION}.${CI_COMMIT_SHORT_SHA}" +DATE="$(date -Iseconds)" +case "${CI_PIPELINE_SOURCE}" in + push) + OUTNAME="${BASENAME}-${BUILD}" + ;; + schedule) + OUTNAME="${BASENAME}-${DATE}" + ;; + web) + OUTNAME="${BASENAME}-${VERSION}" + ;; +esac + +git archive --format tar --prefix ${OUTNAME}/ ${CI_COMMIT_SHA} | gzip > ${OUTDIR}/${OUTNAME}.tar.gz +echo size: +gzip -l ${OUTDIR}/${OUTNAME}.tar.gz + +echo "LIBNITROKEY_BUILD_VERSION=\"${VERSION}\"" >> ./metadata +echo "LIBNITROKEY_BUILD_ID=\"${BUILD}\"" >> ./metadata +echo "LIBNITROKEY_BUILD_DATE=\"${DATE}\"" >> ./metadata +echo "LIBNITROKEY_BUILD_TYPE=\"${CI_PIPELINE_SOURCE}\"" >> ./metadata +echo "LIBNITROKEY_BUILD_OUTNAME=\"${OUTNAME}\"" >> ./metadata +cat ./metadata +pwd +ls +mkdir -p libnitrokey-source-metadata +mv metadata libnitrokey-source-metadata/ +cat libnitrokey-source-metadata/metadata +pushd ${OUTDIR} +sha256sum *.tar.gz > SHA256SUM +popd diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/command_id.cc new/libnitrokey-v3.7/command_id.cc --- old/libnitrokey-v3.6/command_id.cc 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/command_id.cc 2022-04-27 14:35:24.000000000 +0200 @@ -184,6 +184,8 @@ return "SEND_OTP_DATA"; case CommandID::WINK: return "WINK"; + case CommandID::GET_RANDOM: + return "GET_RANDOM"; } return "UNKNOWN"; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/data/41-nitrokey.rules new/libnitrokey-v3.7/data/41-nitrokey.rules --- old/libnitrokey-v3.6/data/41-nitrokey.rules 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/data/41-nitrokey.rules 2022-04-27 14:35:24.000000000 +0200 @@ -31,7 +31,10 @@ KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="4287", TAG+="uaccess" # Nitrokey FIDO2 KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b1", TAG+="uaccess" - +# Nitrokey 3 NFC +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b2", TAG+="uaccess" +# Nitrokey 3 NFC Bootloader +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42dd", TAG+="uaccess" LABEL="u2f_end" @@ -44,8 +47,12 @@ ATTR{idVendor}=="20a0", ATTR{idProduct}=="4107", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", TAG+="uaccess" ## Nitrokey Pro ATTR{idVendor}=="20a0", ATTR{idProduct}=="4108", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", TAG+="uaccess" +## Nitrokey Pro Bootloader +ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b4", TAG+="uaccess" ## Nitrokey Storage ATTR{idVendor}=="20a0", ATTR{idProduct}=="4109", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", TAG+="uaccess" +## Nitrokey Storage Bootloader +ATTR{idVendor}=="03eb", ATTR{idProduct}=="2ff1", TAG+="uaccess" ## Nitrokey Start ATTR{idVendor}=="20a0", ATTR{idProduct}=="4211", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", TAG+="uaccess" ## Nitrokey HSM diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/data/41-nitrokey_old.rules new/libnitrokey-v3.7/data/41-nitrokey_old.rules --- old/libnitrokey-v3.6/data/41-nitrokey_old.rules 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/data/41-nitrokey_old.rules 2022-04-27 14:35:24.000000000 +0200 @@ -32,6 +32,11 @@ KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="4287", MODE="0660", GROUP+="plugdev" # Nitrokey FIDO2 KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b1", MODE="0660", GROUP+="plugdev" +# Nitrokey 3 NFC +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b2", MODE="0660", GROUP+="plugdev" +# Nitrokey 3 NFC Bootloader +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42dd", MODE="0660", GROUP+="plugdev" + LABEL="u2f_end" @@ -43,8 +48,12 @@ ATTR{idVendor}=="20a0", ATTR{idProduct}=="4107", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", MODE="0660", GROUP+="plugdev" ## Nitrokey Pro ATTR{idVendor}=="20a0", ATTR{idProduct}=="4108", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", MODE="0660", GROUP+="plugdev" +## Nitrokey Pro Bootloader +ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b4", MODE="0660", GROUP+="plugdev" ## Nitrokey Storage ATTR{idVendor}=="20a0", ATTR{idProduct}=="4109", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", MODE="0660", GROUP+="plugdev" +## Nitrokey Storage Bootloader +ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2ff1", MODE="0660", GROUP+="plugdev" ## Nitrokey Start ATTR{idVendor}=="20a0", ATTR{idProduct}=="4211", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", MODE="0660", GROUP+="plugdev" ## Nitrokey HSM diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/device.cc new/libnitrokey-v3.7/device.cc --- old/libnitrokey-v3.6/device.cc 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/device.cc 2022-04-27 14:35:24.000000000 +0200 @@ -234,6 +234,7 @@ auto pInfo_ = pInfo; while (pInfo != nullptr){ if (pInfo->path == nullptr || pInfo->serial_number == nullptr) { + pInfo = pInfo->next; continue; } auto deviceModel = product_id_to_model(vendor_id, pInfo->product_id); @@ -328,7 +329,7 @@ } Stick10::Stick10(): - Device(NITROKEY_VID, NITROKEY_PRO_PID, DeviceModel::PRO, 100ms, 5, 100ms) + Device(NITROKEY_VID, NITROKEY_PRO_PID, DeviceModel::PRO, 100ms, 15, 100ms) { setDefaultDelay(); } @@ -342,7 +343,7 @@ LibremKey::LibremKey(): - Device(PURISM_VID, LIBREM_KEY_PID, DeviceModel::LIBREM, 100ms, 5, 100ms) + Device(PURISM_VID, LIBREM_KEY_PID, DeviceModel::LIBREM, 100ms, 15, 100ms) { setDefaultDelay(); } @@ -354,6 +355,10 @@ return ""; #endif + if (total_comm_runs == 0) { + return "Statistics: no connection run"; + } + std::stringstream ss; p(total_comm_runs); p(communication_successful); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/libnitrokey/DeviceCommunicationExceptions.h new/libnitrokey-v3.7/libnitrokey/DeviceCommunicationExceptions.h --- old/libnitrokey-v3.6/libnitrokey/DeviceCommunicationExceptions.h 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/libnitrokey/DeviceCommunicationExceptions.h 2022-04-27 14:35:24.000000000 +0200 @@ -34,7 +34,7 @@ std::string message; static std::atomic_int occurred; public: - DeviceCommunicationException(std::string _msg): std::runtime_error(_msg), message(_msg){ + explicit DeviceCommunicationException(const std::string& _msg): std::runtime_error(_msg), message(_msg){ ++occurred; } uint8_t getType() const {return 1;}; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/libnitrokey/NitrokeyManager.h new/libnitrokey-v3.7/libnitrokey/NitrokeyManager.h --- old/libnitrokey-v3.6/libnitrokey/NitrokeyManager.h 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/libnitrokey/NitrokeyManager.h 2022-04-27 14:35:24.000000000 +0200 @@ -211,7 +211,7 @@ int get_progress_bar_value(); - ~NitrokeyManager(); + virtual ~NitrokeyManager(); bool is_authorization_command_supported(); bool is_320_OTP_secret_supported(); @@ -222,6 +222,7 @@ explicit NitrokeyManager(); void set_log_function(std::function<void(std::string)> log_function); + void set_log_function_raw(std::function<void(const std::string&, Loglevel)> log_function); private: static shared_ptr <NitrokeyManager> _instance; @@ -303,6 +304,8 @@ void change_firmware_update_password_pro(const char *firmware_pin_current, const char *firmware_pin_new); bool is_internal_hotp_slot_number(uint8_t slot_number) const; + + GetRandom::ResponsePayload get_random_pro(uint8_t size_requested); }; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/libnitrokey/command.h new/libnitrokey-v3.7/libnitrokey/command.h --- old/libnitrokey-v3.6/libnitrokey/command.h 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/libnitrokey/command.h 2022-04-27 14:35:24.000000000 +0200 @@ -26,6 +26,7 @@ #include "cxx_semantics.h" #define print_to_ss(x) ( ss << " " << (#x) <<":\t" << (x) << std::endl ); +#define print_to_ss_int(x) ( ss << " " << (#x) <<":\t" << (int)(x) << std::endl ); #ifdef LOG_VOLATILE_DATA #define print_to_ss_volatile(x) print_to_ss(x); #else diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/libnitrokey/command_id.h new/libnitrokey-v3.7/libnitrokey/command_id.h --- old/libnitrokey-v3.6/libnitrokey/command_id.h 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/libnitrokey/command_id.h 2022-04-27 14:35:24.000000000 +0200 @@ -90,6 +90,7 @@ SEND_OTP_DATA = 0x17, FIRMWARE_UPDATE = 0x19, FIRMWARE_PASSWORD_CHANGE = 0x1A, + GET_RANDOM = 0x1B, ENABLE_CRYPTED_PARI = 0x20, DISABLE_CRYPTED_PARI = 0x20 + 1, @@ -147,7 +148,7 @@ PW_SAFE_SEND_DATA = 0x69, //@unused SD_CARD_HIGH_WATERMARK = 0x70, DETECT_SC_AES = 0x6a, - NEW_AES_KEY = 0x6b + NEW_AES_KEY = 0x6b, }; const char *commandid_to_string(CommandID id); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/libnitrokey/device.h new/libnitrokey-v3.7/libnitrokey/device.h --- old/libnitrokey-v3.6/libnitrokey/device.h 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/libnitrokey/device.h 2022-04-27 14:35:24.000000000 +0200 @@ -141,8 +141,8 @@ // lack of device is not actually an error, // so it doesn't throw - virtual bool connect(); - virtual bool disconnect(); + bool connect(); + bool disconnect(); /* * Sends packet of HID_REPORT_SIZE. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/libnitrokey/device_proto.h new/libnitrokey-v3.7/libnitrokey/device_proto.h --- old/libnitrokey-v3.6/libnitrokey/device_proto.h 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/libnitrokey/device_proto.h 2022-04-27 14:35:24.000000000 +0200 @@ -187,7 +187,7 @@ bzero(&p, sizeof(p)); } - ~ClearingProxy() { + virtual ~ClearingProxy() { bzero(&packet, sizeof(packet)); } @@ -244,7 +244,7 @@ LOG(__FUNCTION__, Loglevel::DEBUG_L2); if (dev == nullptr){ - LOG(std::string("Throw: Device not initialized"), Loglevel::DEBUG_L1); + LOG(std::string("Connection not established yet"), Loglevel::DEBUG_L2); throw DeviceNotConnected("Device not initialized"); } dev->m_counters.total_comm_runs++; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/libnitrokey/log.h new/libnitrokey-v3.7/libnitrokey/log.h --- old/libnitrokey-v3.6/libnitrokey/log.h 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/libnitrokey/log.h 2022-04-27 14:35:24.000000000 +0200 @@ -67,6 +67,15 @@ }; + class RawFunctionalLogHandler : public LogHandler { + using log_function_type = std::function<void(const std::string&, Loglevel lvl)>; + log_function_type log_function; + public: + RawFunctionalLogHandler(log_function_type _log_function); + virtual void print(const std::string &, Loglevel lvl); + + }; + extern StdlogHandler stdlog_handler; class Log { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/libnitrokey/stick10_commands.h new/libnitrokey-v3.7/libnitrokey/stick10_commands.h --- old/libnitrokey-v3.6/libnitrokey/stick10_commands.h 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/libnitrokey/stick10_commands.h 2022-04-27 14:35:24.000000000 +0200 @@ -887,6 +887,41 @@ }; +class GetRandom : Command<CommandID::GET_RANDOM> { + public: + static const int DATA_SIZE_MAX = 64 - 13; + struct CommandPayload { + uint8_t size_requested; + + bool isValid() const { return size_requested < DATA_SIZE_MAX; } + std::string dissect() const { + std::stringstream ss; + print_to_ss_int(size_requested); + return ss.str(); + } + } __packed; + + struct ResponsePayload { + uint8_t op_success; + uint8_t size_effective; + uint8_t data[DATA_SIZE_MAX]; + + bool isValid() const { return true; } + std::string dissect() const { + std::stringstream ss; + print_to_ss_int(op_success); + print_to_ss_int(size_effective); + hexdump_to_ss(data); + return ss.str(); + } + } __packed; + + + typedef Transaction<command_id(), struct CommandPayload, struct ResponsePayload> + CommandTransaction; + +}; + class FirmwareUpdate : Command<CommandID::FIRMWARE_UPDATE> { public: struct CommandPayload { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/libnitrokey.pro new/libnitrokey-v3.7/libnitrokey.pro --- old/libnitrokey-v3.6/libnitrokey.pro 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/libnitrokey.pro 2022-04-27 14:35:24.000000000 +0200 @@ -7,11 +7,14 @@ TEMPLATE = lib TARGET = nitrokey -VERSION = 3.6.0 +VERSION = 3.7.0 +LIBNK_VERSION_MAJOR = 3 +LIBNK_VERSION_MINOR = 7 + QMAKE_TARGET_COMPANY = Nitrokey QMAKE_TARGET_PRODUCT = libnitrokey QMAKE_TARGET_DESCRIPTION = Communicate with Nitrokey stick devices in a clean and easy manner -QMAKE_TARGET_COPYRIGHT = Copyright (c) 2015-2020 Nitrokey Gmbh +QMAKE_TARGET_COPYRIGHT = Copyright (c) 2015-2022 Nitrokey Gmbh HEADERS = \ $$PWD/hidapi/hidapi/hidapi.h \ @@ -79,8 +82,6 @@ $$PWD/unittest \ $$PWD/unittest/Catch/single_include -#DEFINES = - unix:!macx{ # Install rules for QMake (CMake is preffered though) udevrules.path = $$system(pkg-config --variable=udevdir udev) @@ -100,3 +101,7 @@ libbin.path = /usr/local/lib INSTALLS += libbin } + +DEFINES += LIBNK_GIT_VERSION=\"\\\"$$system(git describe --abbrev=4 --always)\\\"\" LIBNK_VERSION="\\\"$${VERSION}\\\"" +DEFINES += LIBNK_VERSION_MAJOR=$${LIBNK_VERSION_MAJOR} LIBNK_VERSION_MINOR=$${LIBNK_VERSION_MINOR} +message($$DEFINES) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/log.cc new/libnitrokey-v3.7/log.cc --- old/libnitrokey-v3.6/log.cc 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/log.cc 2022-04-27 14:35:24.000000000 +0200 @@ -55,6 +55,7 @@ void Log::operator()(const std::string &logstr, Loglevel lvl) { if (mp_loghandler != nullptr){ + // FIXME crashes on exit because static object under mp_loghandler is not valid anymore, see NitrokeyManager::set_log_function if ((int) lvl <= (int) m_loglevel) mp_loghandler->print(prefix+logstr, lvl); } } @@ -77,6 +78,10 @@ log_function(s); } + void RawFunctionalLogHandler::print(const std::string &str, Loglevel lvl) { + log_function(str, lvl); + } + std::string LogHandler::format_message_to_string(const std::string &str, const Loglevel &lvl) { static bool last_short = false; if (str.length() == 1){ @@ -99,5 +104,9 @@ FunctionalLogHandler::FunctionalLogHandler(log_function_type _log_function) { log_function = _log_function; } + + RawFunctionalLogHandler::RawFunctionalLogHandler(log_function_type _log_function) { + log_function = _log_function; + } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/meson.build new/libnitrokey-v3.7/meson.build --- old/libnitrokey-v3.6/meson.build 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/meson.build 2022-04-27 14:35:24.000000000 +0200 @@ -1,6 +1,6 @@ project( 'libnitrokey', 'cpp', - version : '3.6.0-rc1', + version : '3.7.0', license : 'LGPL-3.0+', default_options : [ 'cpp_std=c++14' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/unittest/conftest.py new/libnitrokey-v3.7/unittest/conftest.py --- old/libnitrokey-v3.6/unittest/conftest.py 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/unittest/conftest.py 2022-04-27 14:35:24.000000000 +0200 @@ -76,8 +76,11 @@ return get_library(request, allow_offline=True) -@pytest.fixture(scope="module") +@pytest.fixture(scope="session") def C(request=None): + import platform + print(f"Python version: {platform.python_version()}") + print(f"OS: {platform.system()} {platform.release()} {platform.version()}") print("Getting library with connection initialized") return get_library(request) @@ -164,6 +167,9 @@ def pytest_addoption(parser): + parser.addoption( + "--runslow", action="store_true", default=False, help="run slow tests" + ) parser.addoption("--run-skipped", action="store_true", help="run the tests skipped by default, e.g. adding side effects") @@ -171,6 +177,10 @@ if 'skip_by_default' in item.keywords and not item.config.getoption("--run-skipped"): pytest.skip("need --run-skipped option to run this test") +def pytest_configure(config): + config.addinivalue_line("markers", "slow: mark test as slow to run") + + def library_device_reconnect(C): C.NK_logout() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/unittest/constants.py new/libnitrokey-v3.7/unittest/constants.py --- old/libnitrokey-v3.6/unittest/constants.py 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/unittest/constants.py 2022-04-27 14:35:24.000000000 +0200 @@ -18,7 +18,11 @@ SPDX-License-Identifier: LGPL-3.0 """ +from enum import Enum +from sys import stderr + from misc import to_hex, bb +from conftest import print RFC_SECRET_HR = '12345678901234567890' RFC_SECRET = to_hex(RFC_SECRET_HR) # '31323334353637383930...' @@ -39,23 +43,52 @@ UPDATE_TOO_SHORT = UPDATE_LONG[:7] -class DeviceErrorCode: +class DeviceErrorCode(Enum): STATUS_OK = 0 BUSY = 1 # busy or busy progressbar in place of wrong_CRC status NOT_PROGRAMMED = 3 WRONG_PASSWORD = 4 STATUS_NOT_AUTHORIZED = 5 - STATUS_AES_DEC_FAILED = 0xa + STATUS_AES_DEC_FAILED = 10 + STATUS_WRONG_SLOT = 2 + STATUS_TIMESTAMP_WARNING = 6 + STATUS_NO_NAME_ERROR = 7 + STATUS_NOT_SUPPORTED = 8 + STATUS_UNKNOWN_COMMAND = 9 + STATUS_AES_CREATE_KEY_FAILED = 11 + STATUS_ERROR_CHANGING_USER_PASSWORD = 12 + STATUS_ERROR_CHANGING_ADMIN_PASSWORD = 13 + STATUS_ERROR_UNBLOCKING_PIN = 14 + STATUS_UNKNOWN_ERROR = 100 STATUS_DISCONNECTED = 255 + def __eq__(self, other): + other_name = 'Unknown' + try: + other_name = str(DeviceErrorCode(other).name) + except: + pass + result = self.value == other + print(f'Returned {other_name}, expected {self.name} => {result}') + return result -class LibraryErrors: +class LibraryErrors(Enum): TOO_LONG_STRING = 200 INVALID_SLOT = 201 INVALID_HEX_STRING = 202 TARGET_BUFFER_SIZE_SMALLER_THAN_SOURCE = 203 + def __eq__(self, other): + other_name = 'Unknown' + try: + other_name = str(LibraryErrors(other).name) + except: + pass + result = self.value == other + print(f'Returned {other_name}, expected {self.name} => {result}') + return result + HOTP_slot_count = 3 TOTP_slot_count = 15 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/unittest/pyproject.toml new/libnitrokey-v3.7/unittest/pyproject.toml --- old/libnitrokey-v3.6/unittest/pyproject.toml 1970-01-01 01:00:00.000000000 +0100 +++ new/libnitrokey-v3.7/unittest/pyproject.toml 2022-04-27 14:35:24.000000000 +0200 @@ -0,0 +1,22 @@ +[tool.pytest.ini_options] +markers = [ + "slow: marks tests as slow (deselect with '-m \"not slow\"')", + "serial", + "aes", + "encrypted", + "factory_reset", + "firmware", + "hidden", + "info", + "lock_device", + "other", + "otp", + "pin", + "PWS", + "skip", + "skip_by_default", + "slowtest", + "status", + "unencrypted", + "update", +] \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/unittest/requirements.txt new/libnitrokey-v3.7/unittest/requirements.txt --- old/libnitrokey-v3.6/unittest/requirements.txt 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/unittest/requirements.txt 2022-04-27 14:35:24.000000000 +0200 @@ -4,3 +4,4 @@ pytest-randomly tqdm oath +hypothesis diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/unittest/test_issues.py new/libnitrokey-v3.7/unittest/test_issues.py --- old/libnitrokey-v3.6/unittest/test_issues.py 1970-01-01 01:00:00.000000000 +0100 +++ new/libnitrokey-v3.7/unittest/test_issues.py 2022-04-27 14:35:24.000000000 +0200 @@ -0,0 +1,96 @@ +from enum import Enum + +import pytest + +from conftest import skip_if_device_version_lower_than +from constants import DefaultPasswords, DeviceErrorCode +from misc import gs, ffi +from test_pro import check_HOTP_RFC_codes, test_random + + +def test_destroy_encrypted_data_leaves_OTP_intact(C): + """ + Make sure AES key regeneration will not remove OTP records. + Test for Nitrokey Storage. + Details: https://github.com/Nitrokey/libnitrokey/issues/199 + """ + skip_if_device_version_lower_than({'S': 55}) + + assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK + # write password safe slot + assert C.NK_write_password_safe_slot(0, b'slotname1', b'login1', b'pass1') == DeviceErrorCode.STATUS_OK + # read slot + assert gs(C.NK_get_password_safe_slot_name(0)) == b'slotname1' + assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK + slot_login = C.NK_get_password_safe_slot_login(0) + assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK + assert gs(slot_login) == b'login1' + + # write OTP + use_8_digits = False + use_pin_protection = False + assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert C.NK_write_config(255, 255, 255, use_pin_protection, not use_pin_protection, + DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + check_HOTP_RFC_codes(C, lambda x: gs(C.NK_get_hotp_code(x)), use_8_digits=use_8_digits) + + # confirm OTP + assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert gs(C.NK_get_hotp_slot_name(1)) == b'python_test' + + # destroy password safe by regenerating aes key + assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK + + assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK + assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK + assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK + assert gs(C.NK_get_password_safe_slot_name(0)) != b'slotname1' + assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK + + # confirm OTP + assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert gs(C.NK_get_hotp_slot_name(1)) == b'python_test' + + +class Modes(Enum): + EmptyBody = 0 + FactoryResetWithAES = 1 + FactoryReset = 2 + AESGeneration = 3 + +@pytest.mark.firmware +@pytest.mark.factory_reset +@pytest.mark.parametrize("mode", map(Modes, reversed(range(4)))) +def test_pro_factory_reset_breaks_update_password(C, mode: Modes): + from test_pro_bootloader import test_bootloader_password_change_pro, test_bootloader_password_change_pro_length + from test_pro import test_factory_reset + skip_if_device_version_lower_than({'P': 14}) + + func = { + Modes.EmptyBody: lambda: True, + Modes.FactoryResetWithAES: lambda: test_factory_reset(C) or True, + Modes.FactoryReset: lambda: C.NK_factory_reset(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK, + Modes.AESGeneration: lambda: C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK, + } + + def boot_test(C): + test_bootloader_password_change_pro(C) + # test_bootloader_password_change_pro_length(C) + + def random(C): + data = ffi.new('struct GetRandom_t *') + req_count = 50 + res = C.NK_get_random(req_count, data) + assert res == DeviceErrorCode.STATUS_OK + assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK + assert data.op_success == 1 + assert data.size_effective == req_count + + random(C) + boot_test(C) + random(C) + func[mode]() + random(C) # fails here + boot_test(C) + random(C) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/unittest/test_pro.py new/libnitrokey-v3.7/unittest/test_pro.py --- old/libnitrokey-v3.6/unittest/test_pro.py 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/unittest/test_pro.py 2022-04-27 14:35:24.000000000 +0200 @@ -18,16 +18,25 @@ SPDX-License-Identifier: LGPL-3.0 """ +import time +from binascii import hexlify +from math import floor import pytest from conftest import skip_if_device_version_lower_than -from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET, bbRFC_SECRET, LibraryErrors, HOTP_slot_count, \ +from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET, bbRFC_SECRET, HOTP_slot_count, \ TOTP_slot_count from helpers import helper_PWS_get_slotname, helper_PWS_get_loginname, helper_PWS_get_pass from misc import ffi, gs, wait, cast_pointer_to_tuple, has_binary_counter, bb from misc import is_storage + +@pytest.mark.aes +def test_regenerate_aes_key_2(C): + test_regenerate_aes_key(C) + + @pytest.mark.lock_device @pytest.mark.PWS def test_enable_password_safe(C): @@ -161,9 +170,13 @@ if is_storage(C): assert C.NK_clear_new_sd_card_warning(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK enable_password_safe_result = C.NK_enable_password_safe(DefaultPasswords.USER) - assert enable_password_safe_result == DeviceErrorCode.STATUS_AES_DEC_FAILED \ - or is_storage(C) and enable_password_safe_result in \ - [DeviceErrorCode.WRONG_PASSWORD, DeviceErrorCode.STATUS_UNKNOWN_ERROR] # UNKNOWN_ERROR since v0.51 + pro_case = enable_password_safe_result in [DeviceErrorCode.STATUS_AES_DEC_FAILED, + DeviceErrorCode.STATUS_AES_CREATE_KEY_FAILED] # STATUS_AES_CREATE_KEY_FAILED since v0.14 + storage_case = enable_password_safe_result in [DeviceErrorCode.WRONG_PASSWORD, + DeviceErrorCode.STATUS_UNKNOWN_ERROR] # UNKNOWN_ERROR since v0.51 + assert not is_storage(C) and pro_case \ + or is_storage(C) and storage_case + C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK @@ -243,6 +256,7 @@ @pytest.mark.lock_device @pytest.mark.pin def test_user_retry_counts_change_PIN(C): + # TODO can get device with AES key not set, and fail because of that - generate the key or reorder tests assert C.NK_change_user_PIN(DefaultPasswords.USER, DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK wrong_password = b'wrong_password' default_user_retry_count = 3 @@ -659,13 +673,13 @@ assert not config_st.disable_user_password # use structs: write - config_st.numlock = 3 + config_st.numlock = 0xFF assert C.NK_write_config_struct(config_st[0], DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK # use structs: read II err = C.NK_read_config_struct(config_st) assert err == 0 - assert config_st.numlock == 3 + assert config_st.numlock == 0xFF assert config_st.capslock == 1 assert config_st.scrolllock == 2 assert config_st.enable_user_password @@ -1070,3 +1084,35 @@ all_codes.append(this_loop_codes) from pprint import pprint pprint(all_codes) + + +@pytest.mark.parametrize("count",[16, 32, 50, 51]) +def test_random(C, count): + skip_if_device_version_lower_than({'P': 14, 'S': 99}) + data = ffi.new('struct GetRandom_t *') + req_count = count + res = C.NK_get_random(req_count, data) + assert res == DeviceErrorCode.STATUS_OK + assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK + assert data.op_success == 1 + assert data.size_effective == req_count + print(f'{hexlify(bytes(data.data))}') + + +def test_random_collect(C): + skip_if_device_version_lower_than({'P': 14, 'S': 99}) + collected = b'' + data = ffi.new('struct GetRandom_t *') + req_count = 50 + tic = time.perf_counter() + for i in range(1024//req_count+1): + res = C.NK_get_random(req_count, data) + assert res == DeviceErrorCode.STATUS_OK + assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK + assert data.op_success == 1 + assert data.size_effective == req_count + collected += bytes(data.data) + toc = time.perf_counter() + assert len(collected) > 1024 + print(hexlify(collected)) + print(f'Time used to collect {len(collected)} bytes: {round(toc-tic,2)} -> {floor(len(collected) / (toc-tic))} Bps') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/unittest/test_pro_bootloader.py new/libnitrokey-v3.7/unittest/test_pro_bootloader.py --- old/libnitrokey-v3.6/unittest/test_pro_bootloader.py 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/unittest/test_pro_bootloader.py 2022-04-27 14:35:24.000000000 +0200 @@ -60,8 +60,9 @@ @pytest.mark.skip_by_default @pytest.mark.firmware -def test_bootloader_data_rention(C): +def test_bootloader_data_retention(C): skip_if_device_version_lower_than({'P': 11}) + # Not enabled due to lack of side-effect removal at this point assert helper_populate_device(C) assert C.NK_enable_firmware_update_pro(DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_DISCONNECTED @@ -69,3 +70,20 @@ C = library_device_reconnect(C) assert helper_check_device_for_data(C) + +@pytest.mark.firmware +def test_factory_reset_does_not_change_update_password(C): + """ + Check if factory reset changes the update password, which should not happen + """ + skip_if_device_version_lower_than({'P': 13}) + from test_pro import test_factory_reset + # Revert effects of a broken test run, if needed + C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE_TEMP, DefaultPasswords.UPDATE) + + # actual test + assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE, DefaultPasswords.UPDATE_TEMP) == DeviceErrorCode.STATUS_OK + test_factory_reset(C) + assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE, DefaultPasswords.UPDATE_TEMP) == DeviceErrorCode.WRONG_PASSWORD + assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE_TEMP, DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_OK + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/unittest/test_storage.py new/libnitrokey-v3.7/unittest/test_storage.py --- old/libnitrokey-v3.6/unittest/test_storage.py 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/unittest/test_storage.py 2022-04-27 14:35:24.000000000 +0200 @@ -18,9 +18,14 @@ SPDX-License-Identifier: LGPL-3.0 """ - +import dataclasses import pprint +import secrets +from datetime import datetime, timedelta +from time import sleep + import pytest +from hypothesis import given, strategies as st, settings, Verbosity, assume from conftest import skip_if_device_version_lower_than from constants import DefaultPasswords, DeviceErrorCode @@ -43,6 +48,15 @@ return d +def get_status_storage(C): + status_pointer = C.NK_get_status_storage_as_string() + assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK + status_string = gs(status_pointer) + assert len(status_string) > 0 + status_dict = get_dict_from_dissect(status_string.decode('ascii')) + # assert int(status_dict['AdminPwRetryCount']) == default_admin_password_retry_count + return status_dict, C.NK_get_last_command_status() + @pytest.mark.other @pytest.mark.info def test_get_status_storage(C): @@ -350,17 +364,14 @@ wrong_password = b'aaaaaaaaaaa' assert C.NK_change_update_password(wrong_password, DefaultPasswords.UPDATE_TEMP) == DeviceErrorCode.WRONG_PASSWORD assert C.NK_change_update_password(DefaultPasswords.UPDATE, DefaultPasswords.UPDATE_TEMP) == DeviceErrorCode.STATUS_OK + assert C.NK_enable_firmware_update(DefaultPasswords.UPDATE) == DeviceErrorCode.WRONG_PASSWORD assert C.NK_change_update_password(DefaultPasswords.UPDATE_TEMP, DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_OK - -# @pytest.mark.skip(reason='no reversing method added yet') +@pytest.mark.skip_by_default @pytest.mark.update def test_enable_firmware_update(C): skip_if_device_version_lower_than({'S': 50}) - wrong_password = b'aaaaaaaaaaa' - assert C.NK_enable_firmware_update(wrong_password) == DeviceErrorCode.WRONG_PASSWORD - # skip actual test - reason: no reversing method added yet - # assert C.NK_enable_firmware_update(DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_OK + assert C.NK_enable_firmware_update(DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_OK @pytest.mark.other @@ -396,6 +407,8 @@ ) print(info) + +@pytest.mark.skip(reason='additional test') @pytest.mark.other @pytest.mark.firmware def test_export_firmware_extended_fedora29(C): @@ -574,8 +587,97 @@ assert checks_add == checks -def skip_if_not_macos(message:str) -> None: +def skip_if_not_macos(message: str) -> None: import platform if platform.system() != 'Darwin': pytest.skip(message) + + +from typing import Callable, Optional + + +def helper_busy_wait(func: Callable, timeout=10) -> DeviceErrorCode: + start = datetime.now() + res = func() + while res == DeviceErrorCode.BUSY: + sleep(.5) + status_dict, res = get_status_storage(C) + print(status_dict) + if datetime.now() - start > timedelta(seconds=timeout): + print('Timeout, stopping') + return res + return res + + +def helper_create_hidden_volume(C, slot, begin, end, password_in: Optional[bytes], should_work=True): + """ + :param password_in: if None, generate randomly per call + """ + assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK + assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK + + password = password_in + if password_in is None: + password = secrets.token_hex(5) + password = password.encode('ascii') + assert not should_work ^ (helper_busy_wait( + lambda: C.NK_create_hidden_volume(slot, begin, end, password)) == DeviceErrorCode.STATUS_OK) + + assert not should_work ^ ( + helper_busy_wait(lambda: C.NK_unlock_hidden_volume(password)) == DeviceErrorCode.STATUS_OK) + if should_work: + assert helper_busy_wait(lambda: C.NK_lock_hidden_volume()) == DeviceErrorCode.STATUS_OK + + +@dataclasses.dataclass +class MalformedParams: + slot: int + begin: int + end: int + password: Optional[bytes] = None + should_work: bool = True + + def as_list(self): + return [self.slot, self.begin, self.end, self.password, self.should_work] + +@pytest.mark.hidden +@pytest.mark.parametrize("args", [ + # correct calls + MalformedParams(slot=0, begin=1, end=2), + MalformedParams(slot=0, begin=0, end=100), + MalformedParams(slot=0, begin=99, end=100), + MalformedParams(slot=0, begin=0, end=1), + MalformedParams(slot=0, begin=0, end=1), + + # password not empty + # password not longer than 20 characters + MalformedParams(slot=0, begin=1, end=2, password=b"", should_work=False), + MalformedParams(slot=0, begin=1, end=2, password=b"a" * 21, should_work=False), + MalformedParams(slot=0, begin=1, end=2, password=b"a" * 20), + + # slot 0..3 + MalformedParams(slot=5, begin=1, end=2, should_work=False), + MalformedParams(slot=55, begin=1, end=2, should_work=False), + # end 1..100 + MalformedParams(slot=0, begin=0, end=101, should_work=False), + # begin 0..99 + MalformedParams(slot=0, begin=100, end=100, should_work=False), + MalformedParams(slot=0, begin=50, end=49, should_work=False), + MalformedParams(slot=0, begin=50, end=50, should_work=False), +], ids=str) +def test_hidden_volume_malformed_parameters(C, args): + skip_if_device_version_lower_than({'S': 56}) + helper_create_hidden_volume(C, *args.as_list()) + + +# password=st.text(min_size=10, max_size=20) # disabled to avoid failed tests due to unlocking previously correctly +# configured HV with the same password used in failing example +@given(slot=st.integers(min_value=0, max_value=3), begin=st.integers(min_value=0, max_value=99), + end=st.integers(min_value=1, max_value=100)) +@settings(max_examples=15, deadline=timedelta(seconds=10), verbosity=Verbosity.verbose) +def test_hypo_hidden_volume_setup(C, slot, begin, end): + assume(begin < end) + # password = password.encode('utf-8') + password = None + helper_create_hidden_volume(C, slot, begin, end, password, should_work=True) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libnitrokey-v3.6/version.cc new/libnitrokey-v3.7/version.cc --- old/libnitrokey-v3.6/version.cc 2020-09-19 17:11:29.000000000 +0200 +++ new/libnitrokey-v3.7/version.cc 2022-04-27 14:35:24.000000000 +0200 @@ -23,10 +23,16 @@ namespace nitrokey { unsigned int get_major_library_version() { +#ifdef LIBNK_VERSION_MAJOR + return LIBNK_VERSION_MAJOR; +#endif return 3; } unsigned int get_minor_library_version() { +#ifdef LIBNK_VERSION_MINOR + return LIBNK_VERSION_MINOR; +#endif return 0; }