aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore30
-rw-r--r--.gitmodules6
-rw-r--r--.idea/codeStyleSettings.xml38
-rw-r--r--.idea/dictionaries/sz.xml9
-rw-r--r--.idea/vcs.xml9
-rw-r--r--.travis.yml196
-rw-r--r--CMakeLists.txt175
-rw-r--r--CMakeSettings.json37
-rw-r--r--DeviceCommunicationExceptions.cpp3
-rw-r--r--LICENSE166
-rw-r--r--NK_C_API.cc537
-rw-r--r--NK_C_API.h482
-rw-r--r--NitrokeyManager.cc886
-rw-r--r--README.md203
-rw-r--r--TODO1
-rw-r--r--build/.gitignore1
-rw-r--r--command_id.cc152
-rw-r--r--device.cc245
m---------hidapi0
-rw-r--r--include/CommandFailedException.h54
-rw-r--r--include/DeviceCommunicationExceptions.h51
-rw-r--r--include/LibraryException.h97
-rw-r--r--include/LongOperationInProgressException.h28
-rw-r--r--include/NitrokeyManager.h189
-rw-r--r--include/command.h91
-rw-r--r--include/command_id.h124
-rw-r--r--include/cxx_semantics.h23
-rw-r--r--include/device.h142
-rw-r--r--include/device_proto.h467
-rw-r--r--include/dissect.h124
-rw-r--r--include/hidapi/hidapi.h391
-rw-r--r--include/inttypes.h522
-rw-r--r--include/log.h83
-rw-r--r--include/misc.h72
-rw-r--r--include/stick10_commands.h857
-rw-r--r--include/stick10_commands_0.8.h326
-rw-r--r--include/stick20_commands.h351
-rw-r--r--libnitrokey.pc.in10
-rw-r--r--libnitrokey.pro74
-rw-r--r--log.cc71
-rw-r--r--misc.cc101
-rwxr-xr-xpython_bindings_example.py113
-rw-r--r--unittest/Catch/.gitattributes (renamed from .gitattributes)0
-rw-r--r--unittest/Catch/.gitignore22
-rw-r--r--unittest/Catch/.travis.yml163
-rw-r--r--unittest/Catch/LICENSE_1_0.txt (renamed from LICENSE_1_0.txt)0
-rw-r--r--unittest/Catch/README.md22
-rw-r--r--unittest/Catch/catch-logo-small.png (renamed from catch-logo-small.png)bin51470 -> 51470 bytes
-rw-r--r--unittest/Catch/docs/Readme.md (renamed from docs/Readme.md)0
-rw-r--r--unittest/Catch/docs/assertions.md (renamed from docs/assertions.md)0
-rw-r--r--unittest/Catch/docs/build-systems.md (renamed from docs/build-systems.md)0
-rw-r--r--unittest/Catch/docs/command-line.md (renamed from docs/command-line.md)0
-rw-r--r--unittest/Catch/docs/configuration.md (renamed from docs/configuration.md)0
-rw-r--r--unittest/Catch/docs/contributing.md (renamed from docs/contributing.md)0
-rw-r--r--unittest/Catch/docs/logging.md (renamed from docs/logging.md)0
-rw-r--r--unittest/Catch/docs/own-main.md (renamed from docs/own-main.md)0
-rw-r--r--unittest/Catch/docs/slow-compiles.md (renamed from docs/slow-compiles.md)0
-rw-r--r--unittest/Catch/docs/test-cases-and-sections.md (renamed from docs/test-cases-and-sections.md)0
-rw-r--r--unittest/Catch/docs/test-fixtures.md (renamed from docs/test-fixtures.md)0
-rw-r--r--unittest/Catch/docs/tostring.md (renamed from docs/tostring.md)0
-rw-r--r--unittest/Catch/docs/tutorial.md (renamed from docs/tutorial.md)0
-rw-r--r--unittest/Catch/docs/why-catch.md (renamed from docs/why-catch.md)0
-rw-r--r--unittest/Catch/include/catch.hpp (renamed from include/catch.hpp)0
-rw-r--r--unittest/Catch/include/catch_session.hpp (renamed from include/catch_session.hpp)0
-rw-r--r--unittest/Catch/include/catch_with_main.hpp (renamed from include/catch_with_main.hpp)0
-rw-r--r--unittest/Catch/include/external/clara.h (renamed from include/external/clara.h)0
-rw-r--r--unittest/Catch/include/external/tbc_text_format.h (renamed from include/external/tbc_text_format.h)0
-rw-r--r--unittest/Catch/include/internal/catch_approx.hpp (renamed from include/internal/catch_approx.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_assertionresult.h (renamed from include/internal/catch_assertionresult.h)0
-rw-r--r--unittest/Catch/include/internal/catch_assertionresult.hpp (renamed from include/internal/catch_assertionresult.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_capture.hpp (renamed from include/internal/catch_capture.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_clara.h (renamed from include/internal/catch_clara.h)0
-rw-r--r--unittest/Catch/include/internal/catch_commandline.hpp (renamed from include/internal/catch_commandline.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_common.h (renamed from include/internal/catch_common.h)0
-rw-r--r--unittest/Catch/include/internal/catch_common.hpp (renamed from include/internal/catch_common.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_compiler_capabilities.h (renamed from include/internal/catch_compiler_capabilities.h)0
-rw-r--r--unittest/Catch/include/internal/catch_config.hpp (renamed from include/internal/catch_config.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_console_colour.hpp (renamed from include/internal/catch_console_colour.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_console_colour_impl.hpp (renamed from include/internal/catch_console_colour_impl.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_context.h (renamed from include/internal/catch_context.h)0
-rw-r--r--unittest/Catch/include/internal/catch_context_impl.hpp (renamed from include/internal/catch_context_impl.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_debugger.h (renamed from include/internal/catch_debugger.h)0
-rw-r--r--unittest/Catch/include/internal/catch_debugger.hpp (renamed from include/internal/catch_debugger.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_default_main.hpp (renamed from include/internal/catch_default_main.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_evaluate.hpp (renamed from include/internal/catch_evaluate.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_exception_translator_registry.hpp (renamed from include/internal/catch_exception_translator_registry.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_expression_lhs.hpp (renamed from include/internal/catch_expression_lhs.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_fatal_condition.hpp (renamed from include/internal/catch_fatal_condition.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_generators.hpp (renamed from include/internal/catch_generators.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_generators_impl.hpp (renamed from include/internal/catch_generators_impl.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_impl.hpp (renamed from include/internal/catch_impl.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_interfaces_capture.h (renamed from include/internal/catch_interfaces_capture.h)0
-rw-r--r--unittest/Catch/include/internal/catch_interfaces_config.h (renamed from include/internal/catch_interfaces_config.h)0
-rw-r--r--unittest/Catch/include/internal/catch_interfaces_exception.h (renamed from include/internal/catch_interfaces_exception.h)0
-rw-r--r--unittest/Catch/include/internal/catch_interfaces_generators.h (renamed from include/internal/catch_interfaces_generators.h)0
-rw-r--r--unittest/Catch/include/internal/catch_interfaces_registry_hub.h (renamed from include/internal/catch_interfaces_registry_hub.h)0
-rw-r--r--unittest/Catch/include/internal/catch_interfaces_reporter.h (renamed from include/internal/catch_interfaces_reporter.h)0
-rw-r--r--unittest/Catch/include/internal/catch_interfaces_runner.h (renamed from include/internal/catch_interfaces_runner.h)0
-rw-r--r--unittest/Catch/include/internal/catch_interfaces_tag_alias_registry.h (renamed from include/internal/catch_interfaces_tag_alias_registry.h)0
-rw-r--r--unittest/Catch/include/internal/catch_interfaces_testcase.h (renamed from include/internal/catch_interfaces_testcase.h)0
-rw-r--r--unittest/Catch/include/internal/catch_legacy_reporter_adapter.h (renamed from include/internal/catch_legacy_reporter_adapter.h)0
-rw-r--r--unittest/Catch/include/internal/catch_legacy_reporter_adapter.hpp (renamed from include/internal/catch_legacy_reporter_adapter.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_list.hpp (renamed from include/internal/catch_list.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_matchers.hpp (renamed from include/internal/catch_matchers.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_message.h (renamed from include/internal/catch_message.h)0
-rw-r--r--unittest/Catch/include/internal/catch_message.hpp (renamed from include/internal/catch_message.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_notimplemented_exception.h (renamed from include/internal/catch_notimplemented_exception.h)0
-rw-r--r--unittest/Catch/include/internal/catch_notimplemented_exception.hpp (renamed from include/internal/catch_notimplemented_exception.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_objc.hpp (renamed from include/internal/catch_objc.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_objc_arc.hpp (renamed from include/internal/catch_objc_arc.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_option.hpp (renamed from include/internal/catch_option.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_platform.h (renamed from include/internal/catch_platform.h)0
-rw-r--r--unittest/Catch/include/internal/catch_ptr.hpp (renamed from include/internal/catch_ptr.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_reenable_warnings.h (renamed from include/internal/catch_reenable_warnings.h)0
-rw-r--r--unittest/Catch/include/internal/catch_registry_hub.hpp (renamed from include/internal/catch_registry_hub.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_reporter_registrars.hpp (renamed from include/internal/catch_reporter_registrars.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_reporter_registry.hpp (renamed from include/internal/catch_reporter_registry.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_result_builder.h (renamed from include/internal/catch_result_builder.h)0
-rw-r--r--unittest/Catch/include/internal/catch_result_builder.hpp (renamed from include/internal/catch_result_builder.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_result_type.h (renamed from include/internal/catch_result_type.h)0
-rw-r--r--unittest/Catch/include/internal/catch_run_context.hpp (renamed from include/internal/catch_run_context.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_section.h (renamed from include/internal/catch_section.h)0
-rw-r--r--unittest/Catch/include/internal/catch_section.hpp (renamed from include/internal/catch_section.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_section_info.h (renamed from include/internal/catch_section_info.h)0
-rw-r--r--unittest/Catch/include/internal/catch_section_info.hpp (renamed from include/internal/catch_section_info.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_stream.h (renamed from include/internal/catch_stream.h)0
-rw-r--r--unittest/Catch/include/internal/catch_stream.hpp (renamed from include/internal/catch_stream.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_streambuf.h (renamed from include/internal/catch_streambuf.h)0
-rw-r--r--unittest/Catch/include/internal/catch_suppress_warnings.h (renamed from include/internal/catch_suppress_warnings.h)0
-rw-r--r--unittest/Catch/include/internal/catch_tag_alias.h (renamed from include/internal/catch_tag_alias.h)0
-rw-r--r--unittest/Catch/include/internal/catch_tag_alias_registry.h (renamed from include/internal/catch_tag_alias_registry.h)0
-rw-r--r--unittest/Catch/include/internal/catch_tag_alias_registry.hpp (renamed from include/internal/catch_tag_alias_registry.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_test_case_info.h (renamed from include/internal/catch_test_case_info.h)0
-rw-r--r--unittest/Catch/include/internal/catch_test_case_info.hpp (renamed from include/internal/catch_test_case_info.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_test_case_registry_impl.hpp (renamed from include/internal/catch_test_case_registry_impl.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_test_case_tracker.hpp (renamed from include/internal/catch_test_case_tracker.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_test_registry.hpp (renamed from include/internal/catch_test_registry.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_test_spec.hpp (renamed from include/internal/catch_test_spec.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_test_spec_parser.hpp (renamed from include/internal/catch_test_spec_parser.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_text.h (renamed from include/internal/catch_text.h)0
-rw-r--r--unittest/Catch/include/internal/catch_timer.h (renamed from include/internal/catch_timer.h)0
-rw-r--r--unittest/Catch/include/internal/catch_timer.hpp (renamed from include/internal/catch_timer.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_tostring.h (renamed from include/internal/catch_tostring.h)0
-rw-r--r--unittest/Catch/include/internal/catch_tostring.hpp (renamed from include/internal/catch_tostring.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_totals.hpp (renamed from include/internal/catch_totals.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_version.h (renamed from include/internal/catch_version.h)0
-rw-r--r--unittest/Catch/include/internal/catch_version.hpp (renamed from include/internal/catch_version.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_wildcard_pattern.hpp (renamed from include/internal/catch_wildcard_pattern.hpp)0
-rw-r--r--unittest/Catch/include/internal/catch_xmlwriter.hpp (renamed from include/internal/catch_xmlwriter.hpp)0
-rw-r--r--unittest/Catch/include/reporters/catch_reporter_bases.hpp (renamed from include/reporters/catch_reporter_bases.hpp)0
-rw-r--r--unittest/Catch/include/reporters/catch_reporter_compact.hpp (renamed from include/reporters/catch_reporter_compact.hpp)0
-rw-r--r--unittest/Catch/include/reporters/catch_reporter_console.hpp (renamed from include/reporters/catch_reporter_console.hpp)0
-rw-r--r--unittest/Catch/include/reporters/catch_reporter_junit.hpp (renamed from include/reporters/catch_reporter_junit.hpp)0
-rw-r--r--unittest/Catch/include/reporters/catch_reporter_multi.hpp (renamed from include/reporters/catch_reporter_multi.hpp)0
-rw-r--r--unittest/Catch/include/reporters/catch_reporter_teamcity.hpp (renamed from include/reporters/catch_reporter_teamcity.hpp)0
-rw-r--r--unittest/Catch/include/reporters/catch_reporter_xml.hpp (renamed from include/reporters/catch_reporter_xml.hpp)0
-rw-r--r--unittest/Catch/projects/CMake/CMakeLists.txt (renamed from projects/CMake/CMakeLists.txt)0
-rw-r--r--unittest/Catch/projects/SelfTest/ApproxTests.cpp (renamed from projects/SelfTest/ApproxTests.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/BDDTests.cpp (renamed from projects/SelfTest/BDDTests.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/Baselines/console.std.approved.txt (renamed from projects/SelfTest/Baselines/console.std.approved.txt)0
-rw-r--r--unittest/Catch/projects/SelfTest/Baselines/console.sw.approved.txt (renamed from projects/SelfTest/Baselines/console.sw.approved.txt)0
-rw-r--r--unittest/Catch/projects/SelfTest/Baselines/console.swa4.approved.txt (renamed from projects/SelfTest/Baselines/console.swa4.approved.txt)0
-rw-r--r--unittest/Catch/projects/SelfTest/Baselines/junit.sw.approved.txt (renamed from projects/SelfTest/Baselines/junit.sw.approved.txt)0
-rw-r--r--unittest/Catch/projects/SelfTest/Baselines/xml.sw.approved.txt (renamed from projects/SelfTest/Baselines/xml.sw.approved.txt)0
-rw-r--r--unittest/Catch/projects/SelfTest/ClassTests.cpp (renamed from projects/SelfTest/ClassTests.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/CmdLineTests.cpp (renamed from projects/SelfTest/CmdLineTests.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/ConditionTests.cpp (renamed from projects/SelfTest/ConditionTests.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/EnumToString.cpp (renamed from projects/SelfTest/EnumToString.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/ExceptionTests.cpp (renamed from projects/SelfTest/ExceptionTests.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/GeneratorTests.cpp (renamed from projects/SelfTest/GeneratorTests.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/MessageTests.cpp (renamed from projects/SelfTest/MessageTests.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/MiscTests.cpp (renamed from projects/SelfTest/MiscTests.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/PartTrackerTests.cpp (renamed from projects/SelfTest/PartTrackerTests.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/SurrogateCpps/catch_common.cpp (renamed from projects/SelfTest/SurrogateCpps/catch_common.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/SurrogateCpps/catch_console_colour.cpp (renamed from projects/SelfTest/SurrogateCpps/catch_console_colour.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/SurrogateCpps/catch_debugger.cpp (renamed from projects/SelfTest/SurrogateCpps/catch_debugger.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_capture.cpp (renamed from projects/SelfTest/SurrogateCpps/catch_interfaces_capture.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_config.cpp (renamed from projects/SelfTest/SurrogateCpps/catch_interfaces_config.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_exception.cpp (renamed from projects/SelfTest/SurrogateCpps/catch_interfaces_exception.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_generators.cpp (renamed from projects/SelfTest/SurrogateCpps/catch_interfaces_generators.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_registry_hub.cpp (renamed from projects/SelfTest/SurrogateCpps/catch_interfaces_registry_hub.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_reporter.cpp (renamed from projects/SelfTest/SurrogateCpps/catch_interfaces_reporter.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_runner.cpp (renamed from projects/SelfTest/SurrogateCpps/catch_interfaces_runner.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_testcase.cpp (renamed from projects/SelfTest/SurrogateCpps/catch_interfaces_testcase.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/SurrogateCpps/catch_message.cpp (renamed from projects/SelfTest/SurrogateCpps/catch_message.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/SurrogateCpps/catch_option.cpp (renamed from projects/SelfTest/SurrogateCpps/catch_option.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/SurrogateCpps/catch_ptr.cpp (renamed from projects/SelfTest/SurrogateCpps/catch_ptr.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/SurrogateCpps/catch_stream.cpp (renamed from projects/SelfTest/SurrogateCpps/catch_stream.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/SurrogateCpps/catch_streambuf.cpp (renamed from projects/SelfTest/SurrogateCpps/catch_streambuf.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/SurrogateCpps/catch_test_spec.cpp (renamed from projects/SelfTest/SurrogateCpps/catch_test_spec.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/SurrogateCpps/catch_xmlwriter.cpp (renamed from projects/SelfTest/SurrogateCpps/catch_xmlwriter.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/TagAliasTests.cpp (renamed from projects/SelfTest/TagAliasTests.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/TestMain.cpp (renamed from projects/SelfTest/TestMain.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/ToStringPair.cpp (renamed from projects/SelfTest/ToStringPair.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/ToStringTuple.cpp (renamed from projects/SelfTest/ToStringTuple.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/ToStringVector.cpp (renamed from projects/SelfTest/ToStringVector.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/ToStringWhich.cpp (renamed from projects/SelfTest/ToStringWhich.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/TrickyTests.cpp (renamed from projects/SelfTest/TrickyTests.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/VariadicMacrosTests.cpp (renamed from projects/SelfTest/VariadicMacrosTests.cpp)0
-rw-r--r--unittest/Catch/projects/SelfTest/makefile (renamed from projects/SelfTest/makefile)0
-rw-r--r--unittest/Catch/projects/VS2008/TestCatch/TestCatch.sln (renamed from projects/VS2008/TestCatch/TestCatch.sln)0
-rw-r--r--unittest/Catch/projects/VS2008/TestCatch/TestCatch/TestCatch.cpp (renamed from projects/VS2008/TestCatch/TestCatch/TestCatch.cpp)0
-rw-r--r--unittest/Catch/projects/VS2008/TestCatch/TestCatch/TestCatch.vcproj (renamed from projects/VS2008/TestCatch/TestCatch/TestCatch.vcproj)0
-rw-r--r--unittest/Catch/projects/VS2010/TestCatch/TestCatch.sln (renamed from projects/VS2010/TestCatch/TestCatch.sln)0
-rw-r--r--unittest/Catch/projects/VS2010/TestCatch/TestCatch/TestCatch.vcxproj (renamed from projects/VS2010/TestCatch/TestCatch/TestCatch.vcxproj)0
-rw-r--r--unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj (renamed from projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj)0
-rw-r--r--unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata (renamed from projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata)0
-rw-r--r--unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTest/CatchSelfTest.1 (renamed from projects/XCode/CatchSelfTest/CatchSelfTest/CatchSelfTest.1)0
-rw-r--r--unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTest/catch_text.cpp (renamed from projects/XCode/CatchSelfTest/CatchSelfTest/catch_text.cpp)0
-rw-r--r--unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTestSingle.xcodeproj/project.pbxproj (renamed from projects/XCode/CatchSelfTest/CatchSelfTestSingle.xcodeproj/project.pbxproj)0
-rw-r--r--unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTestSingle.xcodeproj/project.xcworkspace/contents.xcworkspacedata (renamed from projects/XCode/CatchSelfTest/CatchSelfTestSingle.xcodeproj/project.xcworkspace/contents.xcworkspacedata)0
-rw-r--r--unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTestSingle.xcodeproj/project.xcworkspace/xcshareddata/CatchSelfTestSingle.xccheckout (renamed from projects/XCode/CatchSelfTest/CatchSelfTestSingle.xcodeproj/project.xcworkspace/xcshareddata/CatchSelfTestSingle.xccheckout)0
-rw-r--r--unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTestSingle/dummy.txt (renamed from projects/XCode/CatchSelfTest/CatchSelfTestSingle/dummy.txt)0
-rw-r--r--unittest/Catch/projects/XCode/OCTest/OCTest.xcodeproj/project.pbxproj (renamed from projects/XCode/OCTest/OCTest.xcodeproj/project.pbxproj)0
-rw-r--r--unittest/Catch/projects/XCode/OCTest/OCTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata (renamed from projects/XCode/OCTest/OCTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata)0
-rw-r--r--unittest/Catch/projects/XCode/OCTest/OCTest.xcodeproj/project.xcworkspace/xcshareddata/OCTest.xccheckout (renamed from projects/XCode/OCTest/OCTest.xcodeproj/project.xcworkspace/xcshareddata/OCTest.xccheckout)0
-rw-r--r--unittest/Catch/projects/XCode/OCTest/OCTest/CatchOCTestCase.h (renamed from projects/XCode/OCTest/OCTest/CatchOCTestCase.h)0
-rw-r--r--unittest/Catch/projects/XCode/OCTest/OCTest/CatchOCTestCase.mm (renamed from projects/XCode/OCTest/OCTest/CatchOCTestCase.mm)0
-rw-r--r--unittest/Catch/projects/XCode/OCTest/OCTest/Main.mm (renamed from projects/XCode/OCTest/OCTest/Main.mm)0
-rw-r--r--unittest/Catch/projects/XCode/OCTest/OCTest/OCTest.1 (renamed from projects/XCode/OCTest/OCTest/OCTest.1)0
-rw-r--r--unittest/Catch/projects/XCode/OCTest/OCTest/OCTest.mm (renamed from projects/XCode/OCTest/OCTest/OCTest.mm)0
-rw-r--r--unittest/Catch/projects/XCode/OCTest/OCTest/TestObj.h (renamed from projects/XCode/OCTest/OCTest/TestObj.h)0
-rw-r--r--unittest/Catch/projects/XCode/OCTest/OCTest/TestObj.m (renamed from projects/XCode/OCTest/OCTest/TestObj.m)0
-rw-r--r--unittest/Catch/projects/XCode/iOSTest/iOSTest.xcodeproj/project.pbxproj (renamed from projects/XCode/iOSTest/iOSTest.xcodeproj/project.pbxproj)0
-rw-r--r--unittest/Catch/projects/XCode/iOSTest/iOSTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata (renamed from projects/XCode/iOSTest/iOSTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata)0
-rw-r--r--unittest/Catch/projects/XCode/iOSTest/iOSTest/OCTest.mm (renamed from projects/XCode/iOSTest/iOSTest/OCTest.mm)0
-rw-r--r--unittest/Catch/projects/XCode/iOSTest/iOSTest/TestObj.h (renamed from projects/XCode/iOSTest/iOSTest/TestObj.h)0
-rw-r--r--unittest/Catch/projects/XCode/iOSTest/iOSTest/TestObj.m (renamed from projects/XCode/iOSTest/iOSTest/TestObj.m)0
-rw-r--r--unittest/Catch/projects/XCode/iOSTest/iOSTest/en.lproj/InfoPlist.strings (renamed from projects/XCode/iOSTest/iOSTest/en.lproj/InfoPlist.strings)0
-rw-r--r--unittest/Catch/projects/XCode/iOSTest/iOSTest/iOSTest-Info.plist (renamed from projects/XCode/iOSTest/iOSTest/iOSTest-Info.plist)0
-rw-r--r--unittest/Catch/projects/XCode/iOSTest/iOSTest/iOSTest-Prefix.pch (renamed from projects/XCode/iOSTest/iOSTest/iOSTest-Prefix.pch)0
-rw-r--r--unittest/Catch/projects/runners/iTchRunner/internal/iTchRunnerAppDelegate.h (renamed from projects/runners/iTchRunner/internal/iTchRunnerAppDelegate.h)0
-rw-r--r--unittest/Catch/projects/runners/iTchRunner/internal/iTchRunnerMainView.h (renamed from projects/runners/iTchRunner/internal/iTchRunnerMainView.h)0
-rw-r--r--unittest/Catch/projects/runners/iTchRunner/internal/iTchRunnerReporter.h (renamed from projects/runners/iTchRunner/internal/iTchRunnerReporter.h)0
-rw-r--r--unittest/Catch/projects/runners/iTchRunner/itChRunnerMain.mm (renamed from projects/runners/iTchRunner/itChRunnerMain.mm)0
-rw-r--r--unittest/Catch/projects/runners/iTchRunner/readme (renamed from projects/runners/iTchRunner/readme)0
-rw-r--r--unittest/Catch/scripts/approvalTests.py (renamed from scripts/approvalTests.py)0
-rw-r--r--unittest/Catch/scripts/approve.py (renamed from scripts/approve.py)0
-rw-r--r--unittest/Catch/scripts/developBuild.py (renamed from scripts/developBuild.py)0
-rw-r--r--unittest/Catch/scripts/fixTrailingWhitespace.py (renamed from scripts/fixTrailingWhitespace.py)0
-rw-r--r--unittest/Catch/scripts/generateSingleHeader.py (renamed from scripts/generateSingleHeader.py)0
-rw-r--r--unittest/Catch/scripts/majorRelease.py (renamed from scripts/majorRelease.py)0
-rw-r--r--unittest/Catch/scripts/minorRelease.py (renamed from scripts/minorRelease.py)0
-rw-r--r--unittest/Catch/scripts/patchRelease.py (renamed from scripts/patchRelease.py)0
-rw-r--r--unittest/Catch/scripts/releaseCommon.py (renamed from scripts/releaseCommon.py)0
-rw-r--r--unittest/Catch/scripts/releaseNotes.py (renamed from scripts/releaseNotes.py)0
-rw-r--r--unittest/Catch/scripts/scriptCommon.py (renamed from scripts/scriptCommon.py)0
-rw-r--r--unittest/Catch/single_include/catch.hpp (renamed from single_include/catch.hpp)0
l---------unittest/build/libnitrokey.so1
-rwxr-xr-xunittest/build/run.sh1
-rw-r--r--unittest/catch_main.cpp2
-rw-r--r--unittest/conftest.py83
-rw-r--r--unittest/constants.py37
-rw-r--r--unittest/misc.py48
-rw-r--r--unittest/requirements.txt4
-rw-r--r--unittest/setup_python_dependencies.sh3
-rw-r--r--unittest/test.cc83
-rw-r--r--unittest/test2.cc212
-rw-r--r--unittest/test3.cc219
-rw-r--r--unittest/test_C_API.cpp34
-rw-r--r--unittest/test_HOTP.cc101
-rw-r--r--unittest/test_command_ids_header.h41
-rw-r--r--unittest/test_issues.cc78
-rw-r--r--unittest/test_library.py68
-rw-r--r--unittest/test_offline.cc141
-rw-r--r--unittest/test_pro.py853
-rw-r--r--unittest/test_storage.py304
267 files changed, 9878 insertions, 179 deletions
diff --git a/.gitignore b/.gitignore
index 3ca4b5c..c98c3a9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,22 +1,10 @@
-*.build
-*.pbxuser
-*.mode1v3
-*.ncb
-*.suo
-Debug
-Release
-*.user
-*.xcuserstate
-.DS_Store
-xcuserdata
-CatchSelfTest.xcscheme
-Breakpoints.xcbkptlist
-projects/VS2010/TestCatch/_UpgradeReport_Files/
-projects/VS2010/TestCatch/TestCatch/TestCatch.vcxproj.filters
-projects/VisualStudio/TestCatch/UpgradeLog.XML
-UpgradeLog.XML
-Resources/DWARF
-projects/XCode/iOSTest/Build
+*.sw*
+*.log
+*.o
+unittest/build/
*.pyc
-DerivedData
-*.xccheckout
+core
+.cache/
+.idea/
+CMakeFiles/
+/.vs
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..4608496
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "unittest/Catch"]
+ path = unittest/Catch
+ url = https://github.com/philsquared/Catch.git
+[submodule "hidapi"]
+ path = hidapi
+ url = https://github.com/Nitrokey/hidapi.git
diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml
new file mode 100644
index 0000000..23f6cae
--- /dev/null
+++ b/.idea/codeStyleSettings.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectCodeStyleSettingsManager">
+ <option name="PER_PROJECT_SETTINGS">
+ <value>
+ <Objective-C-extensions>
+ <option name="GENERATE_INSTANCE_VARIABLES_FOR_PROPERTIES" value="ASK" />
+ <option name="RELEASE_STYLE" value="IVAR" />
+ <option name="TYPE_QUALIFIERS_PLACEMENT" value="BEFORE" />
+ <file>
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
+ </file>
+ <class>
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
+ <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
+ </class>
+ <extensions>
+ <pair source="cpp" header="h" />
+ <pair source="c" header="h" />
+ </extensions>
+ </Objective-C-extensions>
+ </value>
+ </option>
+ <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default (1)" />
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/dictionaries/sz.xml b/.idea/dictionaries/sz.xml
new file mode 100644
index 0000000..c8c18a3
--- /dev/null
+++ b/.idea/dictionaries/sz.xml
@@ -0,0 +1,9 @@
+<component name="ProjectDictionaryState">
+ <dictionary name="sz">
+ <words>
+ <w>loglevel</w>
+ <w>nitrokey</w>
+ <w>totp</w>
+ </words>
+ </dictionary>
+</component> \ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..486a99a
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
+ <mapping directory="$PROJECT_DIR$/hidapi" vcs="Git" />
+ <mapping directory="$PROJECT_DIR$/python_bindings/pybind11" vcs="Git" />
+ <mapping directory="$PROJECT_DIR$/unittest/Catch" vcs="Git" />
+ </component>
+</project> \ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index e63a76a..bf195df 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,163 +1,71 @@
-language: cpp
+language: generic
sudo: false
-
-cache:
- ccache: true
- directories:
- - $HOME/.ccache
-
+os: osx
env:
global:
- - USE_CCACHE=1
- - CCACHE_COMPRESS=1
- - CCACHE_MAXSIZE=200M
- - CCACHE_CPP2=1
-
+ - CF="-DCOMPILE_OFFLINE_TESTS=1 -DERROR_ON_WARNING=ON"
matrix:
include:
-
- # 1/ Linux Clang Builds
+# - osx_image: xcode7.3 #default
+# before_install: &brew
+# - brew update
+# - brew install hidapi
+ - osx_image: xcode9.1
+ - osx_image: xcode8.2
- os: linux
- compiler: clang
- addons: &clang35
+ dist: trusty
+ env: COMPILER_NAME=gcc CXX=g++-5 CC=gcc-5
+ addons:
apt:
- sources: ['llvm-toolchain-precise-3.5', 'ubuntu-toolchain-r-test']
- packages: ['clang-3.5']
- env: COMPILER='ccache clang++-3.5' BUILD_TYPE='Release'
-
+ packages:
+ - cmake
+ - libhidapi-dev
+ - g++-5
+ sources: &sources
+ - ubuntu-toolchain-r-test
- os: linux
- compiler: clang
- addons: *clang35
- env: COMPILER='ccache clang++-3.5' BUILD_TYPE='Debug'
-
-
- - os: linux
- compiler: clang
- addons: &clang36
+ dist: trusty
+ env: COMPILER_NAME=gcc CXX=g++-6 CC=gcc-6
+ addons:
apt:
- sources: ['llvm-toolchain-precise-3.6', 'ubuntu-toolchain-r-test']
- packages: ['clang-3.6']
- env: COMPILER='ccache clang++-3.6' BUILD_TYPE='Release'
-
- - os: linux
- compiler: clang
- addons: *clang36
- env: COMPILER='ccache clang++-3.6' BUILD_TYPE='Debug'
-
-
+ packages:
+ - cmake
+ - libhidapi-dev
+ - g++-6
+ sources: *sources
- os: linux
- compiler: clang
- addons: &clang37
+ dist: trusty
+ env: COMPILER_NAME=gcc CXX=g++-7 CC=gcc-7
+ addons:
apt:
- sources: ['llvm-toolchain-precise-3.7', 'ubuntu-toolchain-r-test']
- packages: ['clang-3.7']
- env: COMPILER='ccache clang++-3.7' BUILD_TYPE='Release'
-
- - os: linux
- compiler: clang
- addons: *clang37
- env: COMPILER='ccache clang++-3.7' BUILD_TYPE='Debug'
-
-
+ packages:
+ - cmake
+ - libhidapi-dev
+ - g++-7
+ sources: *sources
- os: linux
- compiler: clang
- addons: &clang38
+ dist: trusty
+ env: COMPILER_NAME=clang CXX=clang++-3.8 CC=clang-3.8
+ addons:
apt:
- sources: ['llvm-toolchain-precise', 'ubuntu-toolchain-r-test']
- packages: ['clang-3.8']
- env: COMPILER='ccache clang++-3.8' BUILD_TYPE='Release'
-
- - os: linux
- compiler: clang
- addons: *clang38
- env: COMPILER='ccache clang++-3.8' BUILD_TYPE='Debug'
-
-
- # 2/ Linux GCC Builds
- - os: linux
- compiler: gcc
- addons: &gcc48
- apt:
- sources: ['ubuntu-toolchain-r-test']
- packages: ['g++-4.8']
- env: COMPILER='ccache g++-4.8' BUILD_TYPE='Release'
-
- - os: linux
- compiler: gcc
- addons: *gcc48
- env: COMPILER='ccache g++-4.8' BUILD_TYPE='Debug'
-
-
- - os: linux
- compiler: gcc
- addons: &gcc49
- apt:
- sources: ['ubuntu-toolchain-r-test']
- packages: ['g++-4.9']
- env: COMPILER='ccache g++-4.9' BUILD_TYPE='Release'
-
- - os: linux
- compiler: gcc
- addons: *gcc49
- env: COMPILER='ccache g++-4.9' BUILD_TYPE='Debug'
-
-
- - os: linux
- compiler: gcc
- addons: &gcc5
- apt:
- sources: ['ubuntu-toolchain-r-test']
- packages: ['g++-5']
- env: COMPILER='ccache g++-5' BUILD_TYPE='Release'
-
- - os: linux
- compiler: gcc
- addons: *gcc5
- env: COMPILER='ccache g++-5' BUILD_TYPE='Debug'
-
-
- # 3/ OSX Clang Builds
- - os: osx
- osx_image: xcode6.4
- compiler: clang
- env: COMPILER='ccache clang++' BUILD_TYPE='Debug'
-
- - os: osx
- osx_image: xcode6.4
- compiler: clang
- env: COMPILER='ccache clang++' BUILD_TYPE='Release'
-
-
- - os: osx
- osx_image: xcode7
- compiler: clang
- env: COMPILER='ccache clang++' BUILD_TYPE='Debug'
-
- - os: osx
- osx_image: xcode7
- compiler: clang
- env: COMPILER='ccache clang++' BUILD_TYPE='Release'
+ packages:
+ - cmake
+ - libhidapi-dev
+ - g++-5
+ - clang-3.8
+ sources: *sources
install:
- - DEPS_DIR="${TRAVIS_BUILD_DIR}/deps"
- - mkdir -p ${DEPS_DIR} && cd ${DEPS_DIR}
- - |
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then
- CMAKE_URL="http://www.cmake.org/files/v3.3/cmake-3.3.2-Linux-x86_64.tar.gz"
- mkdir cmake && travis_retry wget --quiet -O - ${CMAKE_URL} | tar --strip-components=1 -xz -C cmake
- export PATH=${DEPS_DIR}/cmake/bin:${PATH}
- elif [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
- brew install cmake ccache
- fi
-
-before_script:
- - export CXX=${COMPILER}
- - cd ${TRAVIS_BUILD_DIR}
- - cmake -Hprojects/CMake -BBuild -DCMAKE_BUILD_TYPE=${BUILD_TYPE}
- - cd Build
+ - mkdir -p build
+ - cd build
+# - export CXXFLAGS="${CXX_FLAGS} -Wall -Wextra -Werror" # TODO enable when fixed
+ - ${CXX} --version || true
+ - cmake --version
+ - cmake .. ${CF}
script:
- - make -j 2
- - ctest -V -j 2
+ - make -j2
+ - ctest -VV
+ - mkdir install && make install DESTDIR=install \ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..78d0116
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,175 @@
+# https://cmake.org/pipermail/cmake/2011-May/044166.html
+IF(NOT DEFINED CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_NO_WARNINGS)
+ SET(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_NO_WARNINGS ON)
+ENDIF()
+
+cmake_minimum_required(VERSION 3.1)
+IF (UNIX)
+ OPTION(ADD_ASAN "Use ASAN to show memory issues" FALSE)
+ OPTION(ADD_TSAN "Use TSAN to show thread issues" FALSE)
+ IF(ADD_ASAN)
+ SET(EXTRA_LIBS ${EXTRA_LIBS} asan )
+ ADD_COMPILE_OPTIONS(-fsanitize=address -fno-omit-frame-pointer)
+ ENDIF()
+ IF(ADD_TSAN)
+ SET(EXTRA_LIBS ${EXTRA_LIBS} tsan )
+ SET(USE_CLANG TRUE)
+ ADD_COMPILE_OPTIONS(-fsanitize=thread -fno-omit-frame-pointer -fPIC -g) #use with clang
+ ENDIF()
+ IF(ADD_TSAN AND ADD_ASAN)
+ message(FATAL_ERROR "TSAN and ASAN cannot be used at the same time")
+ ENDIF()
+ENDIF()
+
+project(libnitrokey LANGUAGES C CXX VERSION 3.1.0)
+set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
+
+
+include(GNUInstallDirs)
+
+
+
+IF (NOT CMAKE_BUILD_TYPE)
+ IF(APPLE)
+ # Issues occur when build with enabled optimizations
+ set(CMAKE_BUILD_TYPE Debug)
+ ELSE()
+ set(CMAKE_BUILD_TYPE RelWithDebInfo)
+ ENDIF()
+ENDIF()
+MESSAGE("${PROJECT_NAME}: Build type: ${CMAKE_BUILD_TYPE}")
+
+include_directories(hidapi)
+include_directories(include)
+set(SOURCE_FILES
+ include/command.h
+ include/command_id.h
+ include/cxx_semantics.h
+ include/device.h
+ include/device_proto.h
+ include/dissect.h
+ include/inttypes.h
+ include/log.h
+ include/misc.h
+ include/NitrokeyManager.h
+ include/stick10_commands.h
+ include/stick20_commands.h
+ include/CommandFailedException.h
+ include/LibraryException.h
+ include/LongOperationInProgressException.h
+ include/stick10_commands_0.8.h
+ command_id.cc
+ device.cc
+ log.cc
+ misc.cc
+ NitrokeyManager.cc
+ NK_C_API.h
+ NK_C_API.cc
+ DeviceCommunicationExceptions.cpp)
+
+set(BUILD_SHARED_LIBS ON CACHE BOOL "Build all libraries as shared")
+add_library(nitrokey ${SOURCE_FILES})
+
+IF(APPLE)
+ include_directories(hidapi/hidapi)
+ add_library(hidapi-libusb STATIC hidapi/mac/hid.c )
+ target_link_libraries(hidapi-libusb "-framework CoreFoundation" "-framework IOKit")
+ target_link_libraries(nitrokey hidapi-libusb)
+ELSEIF(UNIX)
+# add_library(hidapi-libusb STATIC hidapi/libusb/hid.c )
+ find_package(PkgConfig)
+ pkg_search_module(HIDAPI_LIBUSB REQUIRED hidapi-libusb)
+ target_compile_options(nitrokey PRIVATE ${HIDAPI_LIBUSB_CFLAGS})
+ target_link_libraries(nitrokey ${HIDAPI_LIBUSB_LDFLAGS})
+ELSEIF(WIN32)
+ include_directories(hidapi/hidapi)
+ add_library(hidapi-libusb STATIC hidapi/windows/hid.c )
+ target_link_libraries(hidapi-libusb setupapi)
+ target_link_libraries(nitrokey hidapi-libusb)
+ENDIF()
+
+set_target_properties(nitrokey PROPERTIES
+ VERSION ${libnitrokey_VERSION}
+ SOVERSION ${libnitrokey_VERSION_MAJOR})
+
+OPTION(ERROR_ON_WARNING "Stop compilation on warning found (not supported for MSVC)" OFF)
+if (NOT MSVC)
+ set(COMPILE_FLAGS "-Wall -Wno-unused-function -Wcast-qual -Woverloaded-virtual")
+ IF(NOT APPLE)
+ if (ERROR_ON_WARNING)
+ set(COMPILE_FLAGS "${COMPILE_FLAGS} -Werror")
+ endif()
+ ENDIF()
+ SET_TARGET_PROPERTIES(nitrokey PROPERTIES COMPILE_FLAGS ${COMPILE_FLAGS} )
+endif()
+
+OPTION(NO_LOG "Compile without logging functionality and its strings (decreases size)" OFF)
+IF (NO_LOG)
+ SET_TARGET_PROPERTIES(nitrokey PROPERTIES COMPILE_DEFINITIONS "NO_LOG")
+ENDIF()
+
+OPTION(LOG_VOLATILE_DATA "Log volatile data (debug)" OFF)
+IF (LOG_VOLATILE_DATA)
+ SET_TARGET_PROPERTIES(nitrokey PROPERTIES COMPILE_DEFINITIONS "LOG_VOLATILE_DATA")
+ENDIF()
+
+
+file(GLOB LIB_INCLUDES "include/*.h")
+install (FILES ${LIB_INCLUDES} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
+install (TARGETS nitrokey DESTINATION ${CMAKE_INSTALL_LIBDIR})
+
+# configure and install pkg-config file
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libnitrokey.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libnitrokey-1.pc @ONLY)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libnitrokey-1.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
+
+OPTION(COMPILE_TESTS "Compile tests" FALSE)
+OPTION(COMPILE_OFFLINE_TESTS "Compile offline tests" FALSE)
+
+IF(COMPILE_OFFLINE_TESTS OR COMPILE_TESTS)
+ include_directories(unittest/Catch/include)
+ add_library(catch STATIC unittest/catch_main.cpp )
+ENDIF()
+
+IF(COMPILE_OFFLINE_TESTS)
+ add_executable (test_offline unittest/test_offline.cc)
+ target_link_libraries (test_offline ${EXTRA_LIBS} nitrokey catch)
+ #run with 'make test' or 'ctest'
+ include (CTest)
+ add_test (runs test_offline)
+ENDIF()
+
+IF (COMPILE_TESTS)
+ #needs connected PRO device for success
+ #warning: it may delete data on the device
+ add_executable (test_C_API unittest/test_C_API.cpp)
+ target_link_libraries (test_C_API ${EXTRA_LIBS} nitrokey catch)
+
+ add_executable (test2 unittest/test2.cc)
+ target_link_libraries (test2 ${EXTRA_LIBS} nitrokey catch)
+
+ add_executable (test3 unittest/test3.cc)
+ target_link_libraries (test3 ${EXTRA_LIBS} nitrokey catch)
+
+ add_executable (test_HOTP unittest/test_HOTP.cc)
+ target_link_libraries (test_HOTP ${EXTRA_LIBS} nitrokey catch)
+
+ add_executable (test1 unittest/test.cc)
+ target_link_libraries (test1 ${EXTRA_LIBS} nitrokey catch)
+
+ add_executable (test_issues unittest/test_issues.cc)
+ target_link_libraries (test_issues ${EXTRA_LIBS} nitrokey catch)
+
+ENDIF()
+
+
+
+#SET(CPACK_GENERATOR
+# "DEB;RPM")
+# build a CPack driven installer package
+include (InstallRequiredSystemLibraries)
+set (CPACK_RESOURCE_FILE_LICENSE
+ "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
+set (CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
+include (CPack)
diff --git a/CMakeSettings.json b/CMakeSettings.json
new file mode 100644
index 0000000..e8c1f1d
--- /dev/null
+++ b/CMakeSettings.json
@@ -0,0 +1,37 @@
+{
+ // See https://go.microsoft.com//fwlink//?linkid=834763 for more information about this file.
+ "configurations": [
+ {
+ "name": "x86-Debug",
+ "generator": "Visual Studio 15 2017",
+ "configurationType" : "Debug",
+ "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
+ "cmakeCommandArgs": "",
+ "buildCommandArgs": "-m -v:minimal"
+ },
+ {
+ "name": "x86-Release",
+ "generator": "Visual Studio 15 2017",
+ "configurationType" : "Release",
+ "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
+ "cmakeCommandArgs": "",
+ "buildCommandArgs": "-m -v:minimal"
+ },
+ {
+ "name": "x64-Debug",
+ "generator": "Visual Studio 15 2017 Win64",
+ "configurationType" : "Debug",
+ "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
+ "cmakeCommandArgs": "",
+ "buildCommandArgs": "-m -v:minimal"
+ },
+ {
+ "name": "x64-Release",
+ "generator": "Visual Studio 15 2017 Win64",
+ "configurationType" : "Release",
+ "buildRoot": "${env.LOCALAPPDATA}\\CMakeBuild\\${workspaceHash}\\build\\${name}",
+ "cmakeCommandArgs": "",
+ "buildCommandArgs": "-m -v:minimal"
+ }
+ ]
+} \ No newline at end of file
diff --git a/DeviceCommunicationExceptions.cpp b/DeviceCommunicationExceptions.cpp
new file mode 100644
index 0000000..a470a48
--- /dev/null
+++ b/DeviceCommunicationExceptions.cpp
@@ -0,0 +1,3 @@
+#include "DeviceCommunicationExceptions.h"
+
+std::atomic_int DeviceCommunicationException::occurred {0};
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..341c30b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,166 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
+
diff --git a/NK_C_API.cc b/NK_C_API.cc
new file mode 100644
index 0000000..e730bae
--- /dev/null
+++ b/NK_C_API.cc
@@ -0,0 +1,537 @@
+#include "NK_C_API.h"
+#include <iostream>
+#include "include/NitrokeyManager.h"
+#include <cstring>
+#include "include/LibraryException.h"
+#include "include/cxx_semantics.h"
+
+#ifdef _MSC_VER
+#ifdef _WIN32
+#pragma message "Using own strndup"
+char * strndup(const char* str, size_t maxlen) {
+ size_t len = strnlen(str, maxlen);
+ char* dup = (char *)malloc(len + 1);
+ memcpy(dup, str, len);
+ dup[len] = 0;
+ return dup;
+}
+#endif
+#endif
+
+using namespace nitrokey;
+
+static uint8_t NK_last_command_status = 0;
+static const int max_string_field_length = 100;
+
+template <typename T>
+T* duplicate_vector_and_clear(std::vector<T> &v){
+ auto d = new T[v.size()];
+ std::copy(v.begin(), v.end(), d);
+ std::fill(v.begin(), v.end(), 0);
+ return d;
+}
+
+template <typename T>
+uint8_t * get_with_array_result(T func){
+ NK_last_command_status = 0;
+ try {
+ return func();
+ }
+ catch (CommandFailedException & commandFailedException){
+ NK_last_command_status = commandFailedException.last_command_status;
+ }
+ catch (LibraryException & libraryException){
+ NK_last_command_status = libraryException.exception_id();
+ }
+ return nullptr;
+}
+
+template <typename T>
+const char* get_with_string_result(T func){
+ NK_last_command_status = 0;
+ try {
+ return func();
+ }
+ catch (CommandFailedException & commandFailedException){
+ NK_last_command_status = commandFailedException.last_command_status;
+ }
+ catch (LibraryException & libraryException){
+ NK_last_command_status = libraryException.exception_id();
+ }
+ return "";
+}
+
+template <typename T>
+auto get_with_result(T func){
+ NK_last_command_status = 0;
+ try {
+ return func();
+ }
+ catch (CommandFailedException & commandFailedException){
+ NK_last_command_status = commandFailedException.last_command_status;
+ }
+ catch (LibraryException & libraryException){
+ NK_last_command_status = libraryException.exception_id();
+ }
+ return static_cast<decltype(func())>(0);
+}
+
+template <typename T>
+uint8_t get_without_result(T func){
+ NK_last_command_status = 0;
+ try {
+ func();
+ return 0;
+ }
+ catch (CommandFailedException & commandFailedException){
+ NK_last_command_status = commandFailedException.last_command_status;
+ }
+ catch (LibraryException & libraryException){
+ NK_last_command_status = libraryException.exception_id();
+ }
+ catch (const InvalidCRCReceived &invalidCRCException){
+ ;
+ }
+ catch (const DeviceCommunicationException &deviceException){
+ NK_last_command_status = 256-deviceException.getType();
+ }
+ return NK_last_command_status;
+}
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ NK_C_API uint8_t NK_get_last_command_status() {
+ auto _copy = NK_last_command_status;
+ NK_last_command_status = 0;
+ return _copy;
+ }
+
+ NK_C_API int NK_login(const char *device_model) {
+ auto m = NitrokeyManager::instance();
+ try {
+ NK_last_command_status = 0;
+ return m->connect(device_model);
+ }
+ catch (CommandFailedException & commandFailedException) {
+ NK_last_command_status = commandFailedException.last_command_status;
+ return commandFailedException.last_command_status;
+ }
+ catch (std::runtime_error &e) {
+ cerr << e.what() << endl;
+ return 0;
+ }
+ return 0;
+ }
+
+ NK_C_API int NK_logout() {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->disconnect();
+ });
+ }
+
+ NK_C_API int NK_first_authenticate(const char* admin_password, const char* admin_temporary_password) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ return m->first_authenticate(admin_password, admin_temporary_password);
+ });
+ }
+
+
+ NK_C_API int NK_user_authenticate(const char* user_password, const char* user_temporary_password) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->user_authenticate(user_password, user_temporary_password);
+ });
+ }
+
+ NK_C_API int NK_factory_reset(const char* admin_password) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->factory_reset(admin_password);
+ });
+ }
+ NK_C_API int NK_build_aes_key(const char* admin_password) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->build_aes_key(admin_password);
+ });
+ }
+
+ NK_C_API int NK_unlock_user_password(const char *admin_password, const char *new_user_password) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->unlock_user_password(admin_password, new_user_password);
+ });
+ }
+
+ NK_C_API int NK_write_config(uint8_t numlock, uint8_t capslock, uint8_t scrolllock, bool enable_user_password,
+ bool delete_user_password,
+ const char *admin_temporary_password) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ return m->write_config(numlock, capslock, scrolllock, enable_user_password, delete_user_password, admin_temporary_password);
+ });
+ }
+
+
+ NK_C_API uint8_t* NK_read_config() {
+ auto m = NitrokeyManager::instance();
+ return get_with_array_result([&]() {
+ auto v = m->read_config();
+ return duplicate_vector_and_clear(v);
+ });
+ }
+
+
+ void clear_string(std::string &s) {
+ std::fill(s.begin(), s.end(), ' ');
+ }
+
+
+ NK_C_API const char * NK_status() {
+ auto m = NitrokeyManager::instance();
+ return get_with_string_result([&]() {
+ string && s = m->get_status_as_string();
+ char * rs = strndup(s.c_str(), max_string_field_length);
+ clear_string(s);
+ return rs;
+ });
+ }
+
+ NK_C_API const char * NK_device_serial_number() {
+ auto m = NitrokeyManager::instance();
+ return get_with_string_result([&]() {
+ string && s = m->get_serial_number();
+ char * rs = strndup(s.c_str(), max_string_field_length);
+ clear_string(s);
+ return rs;
+ });
+ }
+
+ NK_C_API const char * NK_get_hotp_code(uint8_t slot_number) {
+ return NK_get_hotp_code_PIN(slot_number, "");
+ }
+
+ NK_C_API const char * NK_get_hotp_code_PIN(uint8_t slot_number, const char *user_temporary_password) {
+ auto m = NitrokeyManager::instance();
+ return get_with_string_result([&]() {
+ string && s = m->get_HOTP_code(slot_number, user_temporary_password);
+ char * rs = strndup(s.c_str(), max_string_field_length);
+ clear_string(s);
+ return rs;
+ });
+ }
+
+ NK_C_API const char * NK_get_totp_code(uint8_t slot_number, uint64_t challenge, uint64_t last_totp_time,
+ uint8_t last_interval) {
+ return NK_get_totp_code_PIN(slot_number, challenge, last_totp_time, last_interval, "");
+ }
+
+ NK_C_API const char * NK_get_totp_code_PIN(uint8_t slot_number, uint64_t challenge, uint64_t last_totp_time,
+ uint8_t last_interval, const char *user_temporary_password) {
+ auto m = NitrokeyManager::instance();
+ return get_with_string_result([&]() {
+ string && s = m->get_TOTP_code(slot_number, challenge, last_totp_time, last_interval, user_temporary_password);
+ char * rs = strndup(s.c_str(), max_string_field_length);
+ clear_string(s);
+ return rs;
+ });
+ }
+
+ NK_C_API int NK_erase_hotp_slot(uint8_t slot_number, const char *temporary_password) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&] {
+ m->erase_hotp_slot(slot_number, temporary_password);
+ });
+ }
+
+ NK_C_API int NK_erase_totp_slot(uint8_t slot_number, const char *temporary_password) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&] {
+ m->erase_totp_slot(slot_number, temporary_password);
+ });
+ }
+
+ NK_C_API int NK_write_hotp_slot(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) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&] {
+ m->write_HOTP_slot(slot_number, slot_name, secret, hotp_counter, use_8_digits, use_enter, use_tokenID, token_ID,
+ temporary_password);
+ });
+ }
+
+ NK_C_API int NK_write_totp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window,
+ bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
+ const char *temporary_password) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&] {
+ m->write_TOTP_slot(slot_number, slot_name, secret, time_window, use_8_digits, use_enter, use_tokenID, token_ID,
+ temporary_password);
+ });
+ }
+
+ NK_C_API const char* NK_get_totp_slot_name(uint8_t slot_number) {
+ auto m = NitrokeyManager::instance();
+ return get_with_string_result([&]() {
+ const auto slot_name = m->get_totp_slot_name(slot_number);
+ return slot_name;
+ });
+ }
+ NK_C_API const char* NK_get_hotp_slot_name(uint8_t slot_number) {
+ auto m = NitrokeyManager::instance();
+ return get_with_string_result([&]() {
+ const auto slot_name = m->get_hotp_slot_name(slot_number);
+ return slot_name;
+ });
+ }
+
+ NK_C_API void NK_set_debug(bool state) {
+ auto m = NitrokeyManager::instance();
+ m->set_debug(state);
+ }
+
+
+ NK_C_API void NK_set_debug_level(const int level) {
+ auto m = NitrokeyManager::instance();
+ m->set_loglevel(level);
+ }
+
+ NK_C_API int NK_totp_set_time(uint64_t time) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->set_time(time);
+ });
+ }
+
+ NK_C_API int NK_totp_get_time() {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->get_time(0); // FIXME check how that should work
+ });
+ }
+
+ NK_C_API int NK_change_admin_PIN(const char *current_PIN, const char *new_PIN) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->change_admin_PIN(current_PIN, new_PIN);
+ });
+ }
+
+ NK_C_API int NK_change_user_PIN(const char *current_PIN, const char *new_PIN) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->change_user_PIN(current_PIN, new_PIN);
+ });
+ }
+
+ NK_C_API int NK_enable_password_safe(const char *user_pin) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->enable_password_safe(user_pin);
+ });
+ }
+ NK_C_API uint8_t * NK_get_password_safe_slot_status() {
+ auto m = NitrokeyManager::instance();
+ return get_with_array_result([&]() {
+ auto slot_status = m->get_password_safe_slot_status();
+ return duplicate_vector_and_clear(slot_status);
+ });
+
+ }
+
+ NK_C_API uint8_t NK_get_user_retry_count() {
+ auto m = NitrokeyManager::instance();
+ return get_with_result([&]() {
+ return m->get_user_retry_count();
+ });
+ }
+
+ NK_C_API uint8_t NK_get_admin_retry_count() {
+ auto m = NitrokeyManager::instance();
+ return get_with_result([&]() {
+ return m->get_admin_retry_count();
+ });
+ }
+
+ NK_C_API int NK_lock_device() {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->lock_device();
+ });
+ }
+
+ NK_C_API const char *NK_get_password_safe_slot_name(uint8_t slot_number) {
+ auto m = NitrokeyManager::instance();
+ return get_with_string_result([&]() {
+ return m->get_password_safe_slot_name(slot_number);
+ });
+ }
+
+ NK_C_API const char *NK_get_password_safe_slot_login(uint8_t slot_number) {
+ auto m = NitrokeyManager::instance();
+ return get_with_string_result([&]() {
+ return m->get_password_safe_slot_login(slot_number);
+ });
+ }
+ NK_C_API const char *NK_get_password_safe_slot_password(uint8_t slot_number) {
+ auto m = NitrokeyManager::instance();
+ return get_with_string_result([&]() {
+ return m->get_password_safe_slot_password(slot_number);
+ });
+ }
+ NK_C_API int NK_write_password_safe_slot(uint8_t slot_number, const char *slot_name, const char *slot_login,
+ const char *slot_password) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->write_password_safe_slot(slot_number, slot_name, slot_login, slot_password);
+ });
+ }
+
+ NK_C_API int NK_erase_password_safe_slot(uint8_t slot_number) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->erase_password_safe_slot(slot_number);
+ });
+ }
+
+ NK_C_API int NK_is_AES_supported(const char *user_password) {
+ auto m = NitrokeyManager::instance();
+ return get_with_result([&]() {
+ return (uint8_t)m->is_AES_supported(user_password);
+ });
+ }
+
+ NK_C_API int NK_login_auto() {
+ auto m = NitrokeyManager::instance();
+ return get_with_result([&]() {
+ return (uint8_t)m->connect();
+ });
+ }
+
+ // storage commands
+
+ NK_C_API int NK_send_startup(uint64_t seconds_from_epoch) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->send_startup(seconds_from_epoch);
+ });
+ }
+
+ NK_C_API int NK_unlock_encrypted_volume(const char* user_pin) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->unlock_encrypted_volume(user_pin);
+ });
+ }
+
+ NK_C_API int NK_lock_encrypted_volume() {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->lock_encrypted_volume();
+ });
+ }
+
+ NK_C_API int NK_unlock_hidden_volume(const char* hidden_volume_password) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->unlock_hidden_volume(hidden_volume_password);
+ });
+ }
+
+ NK_C_API int NK_lock_hidden_volume() {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->lock_hidden_volume();
+ });
+ }
+
+ NK_C_API int NK_create_hidden_volume(uint8_t slot_nr, uint8_t start_percent, uint8_t end_percent,
+ const char *hidden_volume_password) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->create_hidden_volume(slot_nr, start_percent, end_percent,
+ hidden_volume_password);
+ });
+ }
+
+ NK_C_API int NK_set_unencrypted_read_only(const char* user_pin) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->set_unencrypted_read_only(user_pin);
+ });
+ }
+
+ NK_C_API int NK_set_unencrypted_read_write(const char* user_pin) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->set_unencrypted_read_write(user_pin);
+ });
+ }
+
+ NK_C_API int NK_export_firmware(const char* admin_pin) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->export_firmware(admin_pin);
+ });
+ }
+
+ NK_C_API int NK_clear_new_sd_card_warning(const char* admin_pin) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->clear_new_sd_card_warning(admin_pin);
+ });
+ }
+
+ NK_C_API int NK_fill_SD_card_with_random_data(const char* admin_pin) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->fill_SD_card_with_random_data(admin_pin);
+ });
+ }
+
+ NK_C_API int NK_change_update_password(const char* current_update_password,
+ const char* new_update_password) {
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ m->change_update_password(current_update_password, new_update_password);
+ });
+ }
+
+ NK_C_API const char* NK_get_status_storage_as_string() {
+ auto m = NitrokeyManager::instance();
+ return get_with_string_result([&]() {
+ return m->get_status_storage_as_string();
+ });
+ }
+
+ NK_C_API const char* NK_get_SD_usage_data_as_string() {
+ auto m = NitrokeyManager::instance();
+ return get_with_string_result([&]() {
+ return m->get_SD_usage_data_as_string();
+ });
+ }
+
+ NK_C_API int NK_get_progress_bar_value() {
+ auto m = NitrokeyManager::instance();
+ return get_with_result([&]() {
+ return m->get_progress_bar_value();
+ });
+ }
+
+ NK_C_API int NK_get_major_firmware_version() {
+ auto m = NitrokeyManager::instance();
+ return get_with_result([&]() {
+ return m->get_minor_firmware_version();
+ });
+ }
+
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/NK_C_API.h b/NK_C_API.h
new file mode 100644
index 0000000..3742b43
--- /dev/null
+++ b/NK_C_API.h
@@ -0,0 +1,482 @@
+#ifndef LIBNITROKEY_NK_C_API_H
+#define LIBNITROKEY_NK_C_API_H
+
+#include <stdint.h>
+
+#ifdef _MSC_VER
+#define NK_C_API __declspec(dllexport)
+#else
+#define NK_C_API
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ /**
+ * Set debug level of messages written on stderr
+ * @param state state=True - most messages, state=False - only errors level
+ */
+ NK_C_API void NK_set_debug(bool state);
+
+ /**
+ * Set debug level of messages written on stderr
+ * @param level (int) 0-lowest verbosity, 5-highest verbosity
+ */
+ NK_C_API void NK_set_debug_level(const int level);
+
+ /**
+ * Connect to device of given model. Currently library can be connected only to one device at once.
+ * @param device_model char 'S': Nitrokey Storage, 'P': Nitrokey Pro
+ * @return 1 if connected, 0 if wrong model or cannot connect
+ */
+ NK_C_API int NK_login(const char *device_model);
+
+ /**
+ * Connect to first available device, starting checking from Pro 1st to Storage 2nd.
+ * @return 1 if connected, 0 if wrong model or cannot connect
+ */
+ NK_C_API int NK_login_auto();
+
+ /**
+ * Disconnect from the device.
+ * @return command processing error code
+ */
+ NK_C_API int NK_logout();
+
+ /**
+ * Return the debug status string. Debug purposes.
+ * @return command processing error code
+ */
+ NK_C_API const char * NK_status();
+
+ /**
+ * Return the device's serial number string in hex.
+ * @return string device's serial number in hex
+ */
+ NK_C_API const char * NK_device_serial_number();
+
+ /**
+ * Get last command processing status. Useful for commands which returns the results of their own and could not return
+ * an error code.
+ * @return previous command processing error code
+ */
+ NK_C_API uint8_t NK_get_last_command_status();
+
+ /**
+ * Lock device - cancel any user device unlocking.
+ * @return command processing error code
+ */
+ NK_C_API int NK_lock_device();
+
+ /**
+ * Authenticates the user on USER privilages with user_password and sets user's temporary password on device to user_temporary_password.
+ * @param user_password char[25](Pro) current user password
+ * @param user_temporary_password char[25](Pro) user temporary password to be set on device for further communication (authentication command)
+ * @return command processing error code
+ */
+ NK_C_API int NK_user_authenticate(const char* user_password, const char* user_temporary_password);
+
+ /**
+ * Authenticates the user on ADMIN privilages with admin_password and sets user's temporary password on device to admin_temporary_password.
+ * @param admin_password char[25](Pro) current administrator PIN
+ * @param admin_temporary_password char[25](Pro) admin temporary password to be set on device for further communication (authentication command)
+ * @return command processing error code
+ */
+ NK_C_API int NK_first_authenticate(const char* admin_password, const char* admin_temporary_password);
+
+ /**
+ * Execute a factory reset.
+ * @param admin_password char[20](Pro) current administrator PIN
+ * @return command processing error code
+ */
+ NK_C_API int NK_factory_reset(const char* admin_password);
+
+ /**
+ * Generates AES key on the device
+ * @param admin_password char[20](Pro) current administrator PIN
+ * @return command processing error code
+ */
+ NK_C_API int NK_build_aes_key(const char* admin_password);
+
+ /**
+ * Unlock user PIN locked after 3 incorrect codes tries.
+ * @param admin_password char[20](Pro) current administrator PIN
+ * @return command processing error code
+ */
+ NK_C_API int NK_unlock_user_password(const char *admin_password, const char *new_user_password);
+
+ /**
+ * Write general config to the device
+ * @param numlock set value in range [0-1] to send HOTP code from slot 'numlock' after double pressing numlock
+ * or outside the range to disable this function
+ * @param capslock similar to numlock but with capslock
+ * @param scrolllock similar to numlock but with scrolllock
+ * @param enable_user_password set True to enable OTP PIN protection (request PIN each OTP code request)
+ * @param delete_user_password set True to disable OTP PIN protection (request PIN each OTP code request)
+ * @param admin_temporary_password current admin temporary password
+ * @return command processing error code
+ */
+ NK_C_API int NK_write_config(uint8_t numlock, uint8_t capslock, uint8_t scrolllock,
+ bool enable_user_password, bool delete_user_password, const char *admin_temporary_password);
+
+ /**
+ * Get currently set config - status of function Numlock/Capslock/Scrollock OTP sending and is enabled PIN protected OTP
+ * @see NK_write_config
+ * @return uint8_t general_config[5]:
+ * uint8_t numlock;
+ uint8_t capslock;
+ uint8_t scrolllock;
+ uint8_t enable_user_password;
+ uint8_t delete_user_password;
+
+ */
+ NK_C_API uint8_t* NK_read_config();
+
+ //OTP
+
+ /**
+ * Get name of given TOTP slot
+ * @param slot_number TOTP slot number, slot_number<15
+ * @return char[20](Pro) the name of the slot
+ */
+ NK_C_API const char * NK_get_totp_slot_name(uint8_t slot_number);
+
+ /**
+ *
+ * @param slot_number HOTP slot number, slot_number<3
+ * @return char[20](Pro) the name of the slot
+ */
+ NK_C_API const char * NK_get_hotp_slot_name(uint8_t slot_number);
+
+ /**
+ * Erase HOTP slot data from the device
+ * @param slot_number HOTP slot number, slot_number<3
+ * @param temporary_password admin temporary password
+ * @return command processing error code
+ */
+ NK_C_API int NK_erase_hotp_slot(uint8_t slot_number, const char *temporary_password);
+
+ /**
+ * Erase TOTP slot data from the device
+ * @param slot_number TOTP slot number, slot_number<15
+ * @param temporary_password admin temporary password
+ * @return command processing error code
+ */
+ NK_C_API int NK_erase_totp_slot(uint8_t slot_number, const char *temporary_password);
+
+ /**
+ * Write HOTP slot data to the device
+ * @param slot_number HOTP slot number, slot_number<3
+ * @param slot_name char[15](Pro) desired slot name
+ * @param secret char[20](Pro) 160-bit secret
+ * @param hotp_counter uint32_t starting value of HOTP counter
+ * @param use_8_digits should returned codes be 6 (false) or 8 digits (true)
+ * @param use_enter press ENTER key after sending OTP code using double-pressed scroll/num/capslock
+ * @param use_tokenID @see token_ID
+ * @param token_ID @see https://openauthentication.org/token-specs/, 'Class A' section
+ * @param temporary_password char[25](Pro) admin temporary password
+ * @return command processing error code
+ */
+ NK_C_API int NK_write_hotp_slot(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);
+
+ /**
+ * Write TOTP slot data to the device
+ * @param slot_number TOTP slot number, slot_number<15
+ * @param slot_name char[15](Pro) desired slot name
+ * @param secret char[20](Pro) 160-bit secret
+ * @param time_window uint16_t time window for this TOTP
+ * @param use_8_digits should returned codes be 6 (false) or 8 digits (true)
+ * @param use_enter press ENTER key after sending OTP code using double-pressed scroll/num/capslock
+ * @param use_tokenID @see token_ID
+ * @param token_ID @see https://openauthentication.org/token-specs/, 'Class A' section
+ * @param temporary_password char[20](Pro) admin temporary password
+ * @return command processing error code
+ */
+ NK_C_API int NK_write_totp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window,
+ bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
+ const char *temporary_password);
+
+ /**
+ * Get HOTP code from the device
+ * @param slot_number HOTP slot number, slot_number<3
+ * @return HOTP code
+ */
+ NK_C_API const char * NK_get_hotp_code(uint8_t slot_number);
+
+ /**
+ * Get HOTP code from the device (PIN protected)
+ * @param slot_number HOTP slot number, slot_number<3
+ * @param user_temporary_password char[25](Pro) user temporary password if PIN protected OTP codes are enabled,
+ * otherwise should be set to empty string - ''
+ * @return HOTP code
+ */
+ NK_C_API const char * NK_get_hotp_code_PIN(uint8_t slot_number, const char *user_temporary_password);
+
+ /**
+ * Get TOTP code from the device
+ * @param slot_number TOTP slot number, slot_number<15
+ * @param challenge TOTP challenge
+ * @param last_totp_time last time
+ * @param last_interval last interval
+ * @return TOTP code
+ */
+ NK_C_API const char * NK_get_totp_code(uint8_t slot_number, uint64_t challenge, uint64_t last_totp_time,
+ uint8_t last_interval);
+
+ /**
+ * Get TOTP code from the device (PIN protected)
+ * @param slot_number TOTP slot number, slot_number<15
+ * @param challenge TOTP challenge
+ * @param last_totp_time last time
+ * @param last_interval last interval
+ * @param user_temporary_password char[25](Pro) user temporary password if PIN protected OTP codes are enabled,
+ * otherwise should be set to empty string - ''
+ * @return TOTP code
+ */
+ NK_C_API const char * NK_get_totp_code_PIN(uint8_t slot_number, uint64_t challenge,
+ uint64_t last_totp_time, uint8_t last_interval,
+ const char *user_temporary_password);
+
+ /**
+ * Set time on the device (for TOTP requests)
+ * @param time seconds in unix epoch (from 01.01.1970)
+ * @return command processing error code
+ */
+ NK_C_API int NK_totp_set_time(uint64_t time);
+
+ NK_C_API int NK_totp_get_time();
+ //passwords
+ /**
+ * Change administrator PIN
+ * @param current_PIN char[25](Pro) current PIN
+ * @param new_PIN char[25](Pro) new PIN
+ * @return command processing error code
+ */
+ NK_C_API int NK_change_admin_PIN(const char *current_PIN, const char *new_PIN);
+
+ /**
+ * Change user PIN
+ * @param current_PIN char[25](Pro) current PIN
+ * @param new_PIN char[25](Pro) new PIN
+ * @return command processing error code
+ */
+ NK_C_API int NK_change_user_PIN(const char *current_PIN, const char *new_PIN);
+
+
+ /**
+ * Get retry count of user PIN
+ * @return user PIN retry count
+ */
+ NK_C_API uint8_t NK_get_user_retry_count();
+
+ /**
+ * Get retry count of admin PIN
+ * @return admin PIN retry count
+ */
+ NK_C_API uint8_t NK_get_admin_retry_count();
+ //password safe
+
+ /**
+ * Enable password safe access
+ * @param user_pin char[30](Pro) current user PIN
+ * @return command processing error code
+ */
+ NK_C_API int NK_enable_password_safe(const char *user_pin);
+
+ /**
+ * Get password safe slots' status
+ * @return uint8_t[16] slot statuses - each byte represents one slot with 0 (not programmed) and 1 (programmed)
+ */
+ NK_C_API uint8_t * NK_get_password_safe_slot_status();
+
+ /**
+ * Get password safe slot name
+ * @param slot_number password safe slot number, slot_number<16
+ * @return slot name
+ */
+ NK_C_API const char *NK_get_password_safe_slot_name(uint8_t slot_number);
+
+ /**
+ * Get password safe slot login
+ * @param slot_number password safe slot number, slot_number<16
+ * @return login from the PWS slot
+ */
+ NK_C_API const char *NK_get_password_safe_slot_login(uint8_t slot_number);
+
+ /**
+ * Get the password safe slot password
+ * @param slot_number password safe slot number, slot_number<16
+ * @return password from the PWS slot
+ */
+ NK_C_API const char *NK_get_password_safe_slot_password(uint8_t slot_number);
+
+ /**
+ * Write password safe data to the slot
+ * @param slot_number password safe slot number, slot_number<16
+ * @param slot_name char[11](Pro) name of the slot
+ * @param slot_login char[32](Pro) login string
+ * @param slot_password char[20](Pro) password string
+ * @return command processing error code
+ */
+ NK_C_API int NK_write_password_safe_slot(uint8_t slot_number, const char *slot_name,
+ const char *slot_login, const char *slot_password);
+
+ /**
+ * Erase the password safe slot from the device
+ * @param slot_number password safe slot number, slot_number<16
+ * @return command processing error code
+ */
+ NK_C_API int NK_erase_password_safe_slot(uint8_t slot_number);
+
+ /**
+ * Check whether AES is supported by the device
+ * @return 0 for no and 1 for yes
+ */
+ NK_C_API int NK_is_AES_supported(const char *user_password);
+
+ /**
+ * Get device's major firmware version
+ * @return 7,8 for Pro and major for Storage
+ */
+ NK_C_API int NK_get_major_firmware_version();
+
+
+
+ /**
+ * This command is typically run to initiate
+ * communication with the device (altough not required).
+ * It sets time on device and returns its current status
+ * - a combination of set_time and get_status_storage commands
+ * Storage only
+ * @param seconds_from_epoch date and time expressed in seconds
+ */
+ NK_C_API int NK_send_startup(uint64_t seconds_from_epoch);
+
+ /**
+ * Unlock encrypted volume.
+ * Storage only
+ * @param user_pin user pin 20 characters
+ * @return command processing error code
+ */
+ NK_C_API int NK_unlock_encrypted_volume(const char* user_pin);
+
+ /**
+ * Locks encrypted volume
+ * @return command processing error code
+ */
+ NK_C_API int NK_lock_encrypted_volume();
+
+ /**
+ * Unlock hidden volume and lock encrypted volume.
+ * Requires encrypted volume to be unlocked.
+ * Storage only
+ * @param hidden_volume_password 20 characters
+ * @return command processing error code
+ */
+ NK_C_API int NK_unlock_hidden_volume(const char* hidden_volume_password);
+
+ /**
+ * Locks hidden volume
+ * @return command processing error code
+ */
+ NK_C_API int NK_lock_hidden_volume();
+
+ /**
+ * Create hidden volume.
+ * Requires encrypted volume to be unlocked.
+ * Storage only
+ * @param slot_nr slot number in range 0-3
+ * @param start_percent volume begin expressed in percent of total available storage, int in range 0-99
+ * @param end_percent volume end expressed in percent of total available storage, int in range 1-100
+ * @param hidden_volume_password 20 characters
+ * @return command processing error code
+ */
+ NK_C_API int NK_create_hidden_volume(uint8_t slot_nr, uint8_t start_percent, uint8_t end_percent,
+ const char *hidden_volume_password);
+
+ /**
+ * Make unencrypted volume read-only.
+ * Device hides unencrypted volume for a second therefore make sure
+ * buffers are flushed before running.
+ * Storage only
+ * @param user_pin 20 characters
+ * @return command processing error code
+ */
+ NK_C_API int NK_set_unencrypted_read_only(const char* user_pin);
+
+ /**
+ * Make unencrypted volume read-write.
+ * Device hides unencrypted volume for a second therefore make sure
+ * buffers are flushed before running.
+ * Storage only
+ * @param user_pin 20 characters
+ * @return command processing error code
+ */
+ NK_C_API int NK_set_unencrypted_read_write(const char* user_pin);
+
+ /**
+ * Exports device's firmware to unencrypted volume.
+ * Storage only
+ * @param admin_pin 20 characters
+ * @return command processing error code
+ */
+ NK_C_API int NK_export_firmware(const char* admin_pin);
+
+ /**
+ * Clear new SD card notification. It is set after factory reset.
+ * Storage only
+ * @param admin_pin 20 characters
+ * @return command processing error code
+ */
+ NK_C_API int NK_clear_new_sd_card_warning(const char* admin_pin);
+
+ /**
+ * Fill SD card with random data.
+ * Should be done on first stick initialization after creating keys.
+ * Storage only
+ * @param admin_pin 20 characters
+ * @return command processing error code
+ */
+ NK_C_API int NK_fill_SD_card_with_random_data(const char* admin_pin);
+
+ /**
+ * Change update password.
+ * Update password is used for entering update mode, where firmware
+ * could be uploaded using dfu-programmer or other means.
+ * Storage only
+ * @param current_update_password 20 characters
+ * @param new_update_password 20 characters
+ * @return command processing error code
+ */
+ NK_C_API int NK_change_update_password(const char* current_update_password,
+ const char* new_update_password);
+
+ /**
+ * Get Storage stick status as string.
+ * Storage only
+ * @return string with devices attributes
+ */
+ NK_C_API const char* NK_get_status_storage_as_string();
+
+ /**
+ * Get SD card usage attributes as string.
+ * Usable during hidden volumes creation.
+ * Storage only
+ * @return string with SD card usage attributes
+ */
+ NK_C_API const char* NK_get_SD_usage_data_as_string();
+
+ /**
+ * Get progress value of current long operation.
+ * Storage only
+ * @return int in range 0-100 or -1 if device is not busy
+ */
+ NK_C_API int NK_get_progress_bar_value();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //LIBNITROKEY_NK_C_API_H
diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc
new file mode 100644
index 0000000..b29b27d
--- /dev/null
+++ b/NitrokeyManager.cc
@@ -0,0 +1,886 @@
+#include <cstring>
+#include <iostream>
+#include "include/NitrokeyManager.h"
+#include "include/LibraryException.h"
+#include <algorithm>
+#include <unordered_map>
+#include <stick20_commands.h>
+#include "include/misc.h"
+#include <mutex>
+#include "include/cxx_semantics.h"
+#include <functional>
+
+std::mutex nitrokey::proto::send_receive_mtx;
+
+namespace nitrokey{
+
+ std::mutex mex_dev_com_manager;
+
+#ifndef strndup
+#ifdef _WIN32
+#pragma message "Using own strndup"
+char * strndup(const char* str, size_t maxlen){
+ size_t len = strnlen(str, maxlen);
+ char* dup = (char *) malloc(len + 1);
+ memcpy(dup, str, len);
+ dup[len] = 0;
+ return dup;
+}
+#endif
+#endif
+
+using nitrokey::misc::strcpyT;
+
+ template <typename T>
+ typename T::CommandPayload get_payload(){
+ //Create, initialize and return by value command payload
+ typename T::CommandPayload st;
+ bzero(&st, sizeof(st));
+ return st;
+ }
+
+
+ // package type to auth, auth type [Authorize,UserAuthorize]
+ template <typename S, typename A, typename T>
+ void NitrokeyManager::authorize_packet(T &package, const char *admin_temporary_password, shared_ptr<Device> device){
+ if (!is_authorization_command_supported()){
+ LOG("Authorization command not supported, skipping", Loglevel::WARNING);
+ }
+ auto auth = get_payload<A>();
+ strcpyT(auth.temporary_password, admin_temporary_password);
+ auth.crc_to_authorize = S::CommandTransaction::getCRC(package);
+ A::CommandTransaction::run(device, auth);
+ }
+
+ shared_ptr <NitrokeyManager> NitrokeyManager::_instance = nullptr;
+
+ NitrokeyManager::NitrokeyManager() : device(nullptr)
+ {
+ set_debug(true);
+ }
+ NitrokeyManager::~NitrokeyManager() {
+ }
+
+ bool NitrokeyManager::set_current_device_speed(int retry_delay, int send_receive_delay){
+ if (retry_delay < 20 || send_receive_delay < 20){
+ LOG("Delay set too low: " + to_string(retry_delay) +" "+ to_string(send_receive_delay), Loglevel::WARNING);
+ return false;
+ }
+
+ std::lock_guard<std::mutex> lock(mex_dev_com_manager);
+ if(device == nullptr) {
+ return false;
+ }
+ device->set_receiving_delay(std::chrono::duration<int, std::milli>(send_receive_delay));
+ device->set_retry_delay(std::chrono::duration<int, std::milli>(retry_delay));
+ return true;
+ }
+
+ bool NitrokeyManager::connect() {
+ std::lock_guard<std::mutex> lock(mex_dev_com_manager);
+ vector< shared_ptr<Device> > devices = { make_shared<Stick10>(), make_shared<Stick20>() };
+ for( auto & d : devices ){
+ if (d->connect()){
+ device = std::shared_ptr<Device>(d);
+ }
+ }
+ return device != nullptr;
+ }
+
+
+ void NitrokeyManager::set_log_function(std::function<void(std::string)> log_function){
+ static nitrokey::log::FunctionalLogHandler 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);
+ return false;
+ }
+ Device::set_default_device_speed(delay);
+ return true;
+ }
+
+ bool NitrokeyManager::connect(const char *device_model) {
+ std::lock_guard<std::mutex> lock(mex_dev_com_manager);
+ LOG(__FUNCTION__, nitrokey::log::Loglevel::DEBUG_L2);
+ switch (device_model[0]){
+ case 'P':
+ device = make_shared<Stick10>();
+ break;
+ case 'S':
+ device = make_shared<Stick20>();
+ break;
+ default:
+ throw std::runtime_error("Unknown model");
+ }
+ return device->connect();
+ }
+
+ shared_ptr<NitrokeyManager> NitrokeyManager::instance() {
+ static std::mutex mutex;
+ std::lock_guard<std::mutex> lock(mutex);
+ if (_instance == nullptr){
+ _instance = make_shared<NitrokeyManager>();
+ }
+ return _instance;
+ }
+
+
+
+ bool NitrokeyManager::disconnect() {
+ std::lock_guard<std::mutex> lock(mex_dev_com_manager);
+ return _disconnect_no_lock();
+ }
+
+ bool NitrokeyManager::_disconnect_no_lock() {
+ //do not use directly without locked mutex,
+ //used by could_be_enumerated, disconnect
+ if (device == nullptr){
+ return false;
+ }
+ const auto res = device->disconnect();
+ device = nullptr;
+ return res;
+ }
+
+ bool NitrokeyManager::is_connected() throw(){
+ std::lock_guard<std::mutex> lock(mex_dev_com_manager);
+ if(device != nullptr){
+ auto connected = device->could_be_enumerated();
+ if(connected){
+ return true;
+ } else {
+ _disconnect_no_lock();
+ return false;
+ }
+ }
+ return false;
+ }
+
+ bool NitrokeyManager::could_current_device_be_enumerated() {
+ std::lock_guard<std::mutex> lock(mex_dev_com_manager);
+ if (device != nullptr) {
+ return device->could_be_enumerated();
+ }
+ return false;
+ }
+
+ void NitrokeyManager::set_loglevel(int loglevel) {
+ loglevel = max(loglevel, static_cast<int>(Loglevel::ERROR));
+ loglevel = min(loglevel, static_cast<int>(Loglevel::DEBUG_L2));
+ Log::instance().set_loglevel(static_cast<Loglevel>(loglevel));
+ }
+
+ void NitrokeyManager::set_loglevel(Loglevel loglevel) {
+ Log::instance().set_loglevel(loglevel);
+ }
+
+ void NitrokeyManager::set_debug(bool state) {
+ if (state){
+ Log::instance().set_loglevel(Loglevel::DEBUG);
+ } else {
+ Log::instance().set_loglevel(Loglevel::ERROR);
+ }
+ }
+
+
+ string NitrokeyManager::get_serial_number() {
+ if (device == nullptr) { return ""; };
+ switch (device->get_device_model()) {
+ case DeviceModel::PRO: {
+ auto response = GetStatus::CommandTransaction::run(device);
+ return nitrokey::misc::toHex(response.data().card_serial_u32);
+ }
+ break;
+
+ case DeviceModel::STORAGE:
+ {
+ auto response = stick20::GetDeviceStatus::CommandTransaction::run(device);
+ return nitrokey::misc::toHex(response.data().ActiveSmartCardID_u32);
+ }
+ break;
+ }
+ return "NA";
+ }
+
+ stick10::GetStatus::ResponsePayload NitrokeyManager::get_status(){
+ try{
+ auto response = GetStatus::CommandTransaction::run(device);
+ return response.data();
+ }
+ catch (DeviceSendingFailure &e){
+ disconnect();
+ throw;
+ }
+ }
+
+ string NitrokeyManager::get_status_as_string() {
+ auto response = GetStatus::CommandTransaction::run(device);
+ return response.data().dissect();
+ }
+
+ string getFilledOTPCode(uint32_t code, bool use_8_digits){
+ stringstream s;
+ s << std::right << std::setw(use_8_digits ? 8 : 6) << std::setfill('0') << code;
+ return s.str();
+ }
+
+ string NitrokeyManager::get_HOTP_code(uint8_t slot_number, const char *user_temporary_password) {
+ if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
+
+ if (is_authorization_command_supported()){
+ auto gh = get_payload<GetHOTP>();
+ gh.slot_number = get_internal_slot_number_for_hotp(slot_number);
+ if(user_temporary_password != nullptr && strlen(user_temporary_password)!=0){ //FIXME use string instead of strlen
+ authorize_packet<GetHOTP, UserAuthorize>(gh, user_temporary_password, device);
+ }
+ auto resp = GetHOTP::CommandTransaction::run(device, gh);
+ return getFilledOTPCode(resp.data().code, resp.data().use_8_digits);
+ } else {
+ auto gh = get_payload<stick10_08::GetHOTP>();
+ gh.slot_number = get_internal_slot_number_for_hotp(slot_number);
+ if(user_temporary_password != nullptr && strlen(user_temporary_password)!=0) {
+ strcpyT(gh.temporary_user_password, user_temporary_password);
+ }
+ auto resp = stick10_08::GetHOTP::CommandTransaction::run(device, gh);
+ return getFilledOTPCode(resp.data().code, resp.data().use_8_digits);
+ }
+ return "";
+ }
+
+ bool NitrokeyManager::is_valid_hotp_slot_number(uint8_t slot_number) const { return slot_number < 3; }
+ bool NitrokeyManager::is_valid_totp_slot_number(uint8_t slot_number) const { return slot_number < 0x10-1; } //15
+ uint8_t NitrokeyManager::get_internal_slot_number_for_totp(uint8_t slot_number) const { return (uint8_t) (0x20 + slot_number); }
+ uint8_t NitrokeyManager::get_internal_slot_number_for_hotp(uint8_t slot_number) const { return (uint8_t) (0x10 + slot_number); }
+
+
+
+ string NitrokeyManager::get_TOTP_code(uint8_t slot_number, uint64_t challenge, uint64_t last_totp_time,
+ uint8_t last_interval,
+ const char *user_temporary_password) {
+ if(!is_valid_totp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
+ slot_number = get_internal_slot_number_for_totp(slot_number);
+
+ if (is_authorization_command_supported()){
+ auto gt = get_payload<GetTOTP>();
+ gt.slot_number = slot_number;
+ gt.challenge = challenge;
+ gt.last_interval = last_interval;
+ gt.last_totp_time = last_totp_time;
+
+ if(user_temporary_password != nullptr && strlen(user_temporary_password)!=0){ //FIXME use string instead of strlen
+ authorize_packet<GetTOTP, UserAuthorize>(gt, user_temporary_password, device);
+ }
+ auto resp = GetTOTP::CommandTransaction::run(device, gt);
+ return getFilledOTPCode(resp.data().code, resp.data().use_8_digits);
+ } else {
+ auto gt = get_payload<stick10_08::GetTOTP>();
+ strcpyT(gt.temporary_user_password, user_temporary_password);
+ gt.slot_number = slot_number;
+ auto resp = stick10_08::GetTOTP::CommandTransaction::run(device, gt);
+ return getFilledOTPCode(resp.data().code, resp.data().use_8_digits);
+ }
+ return "";
+ }
+
+ bool NitrokeyManager::erase_slot(uint8_t slot_number, const char *temporary_password) {
+ if (is_authorization_command_supported()){
+ auto p = get_payload<EraseSlot>();
+ p.slot_number = slot_number;
+ authorize_packet<EraseSlot, Authorize>(p, temporary_password, device);
+ auto resp = EraseSlot::CommandTransaction::run(device,p);
+ } else {
+ auto p = get_payload<stick10_08::EraseSlot>();
+ p.slot_number = slot_number;
+ strcpyT(p.temporary_admin_password, temporary_password);
+ auto resp = stick10_08::EraseSlot::CommandTransaction::run(device,p);
+ }
+ return true;
+ }
+
+ bool NitrokeyManager::erase_hotp_slot(uint8_t slot_number, const char *temporary_password) {
+ if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
+ slot_number = get_internal_slot_number_for_hotp(slot_number);
+ return erase_slot(slot_number, temporary_password);
+ }
+
+ bool NitrokeyManager::erase_totp_slot(uint8_t slot_number, const char *temporary_password) {
+ if (!is_valid_totp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
+ slot_number = get_internal_slot_number_for_totp(slot_number);
+ return erase_slot(slot_number, temporary_password);
+ }
+
+ template <typename T, typename U>
+ void vector_copy_ranged(T& dest, std::vector<U> &vec, size_t begin, size_t elements_to_copy){
+ const size_t d_size = sizeof(dest);
+ if(d_size < elements_to_copy){
+ throw TargetBufferSmallerThanSource(elements_to_copy, d_size);
+ }
+ std::fill(dest, dest+d_size, 0);
+ std::copy(vec.begin() + begin, vec.begin() +begin + elements_to_copy, dest);
+ }
+
+ template <typename T, typename U>
+ void vector_copy(T& dest, std::vector<U> &vec){
+ const size_t d_size = sizeof(dest);
+ if(d_size < vec.size()){
+ throw TargetBufferSmallerThanSource(vec.size(), d_size);
+ }
+ std::fill(dest, dest+d_size, 0);
+ std::copy(vec.begin(), vec.end(), dest);
+ }
+
+ bool NitrokeyManager::write_HOTP_slot(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 (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
+
+ int internal_slot_number = get_internal_slot_number_for_hotp(slot_number);
+ if (is_authorization_command_supported()){
+ write_HOTP_slot_authorize(internal_slot_number, slot_name, secret, hotp_counter, use_8_digits, use_enter, use_tokenID,
+ token_ID, temporary_password);
+ } else {
+ write_OTP_slot_no_authorize(internal_slot_number, slot_name, secret, hotp_counter, use_8_digits, use_enter, use_tokenID,
+ token_ID, temporary_password);
+ }
+ return true;
+ }
+
+ 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) {
+ auto payload = get_payload<WriteToHOTPSlot>();
+ payload.slot_number = slot_number;
+ auto secret_bin = misc::hex_string_to_byte(secret);
+ vector_copy(payload.slot_secret, secret_bin);
+ strcpyT(payload.slot_name, slot_name);
+ strcpyT(payload.slot_token_id, token_ID);
+ switch (device->get_device_model() ){
+ case DeviceModel::PRO: {
+ payload.slot_counter = hotp_counter;
+ break;
+ }
+ case DeviceModel::STORAGE: {
+ string counter = to_string(hotp_counter);
+ strcpyT(payload.slot_counter_s, counter.c_str());
+ break;
+ }
+ default:
+ LOG(string(__FILE__) + to_string(__LINE__) +
+ string(__FUNCTION__) + string(" Unhandled device model for HOTP")
+ , Loglevel::DEBUG);
+ break;
+ }
+ payload.use_8_digits = use_8_digits;
+ payload.use_enter = use_enter;
+ payload.use_tokenID = use_tokenID;
+
+ authorize_packet<WriteToHOTPSlot, Authorize>(payload, temporary_password, device);
+
+ auto resp = WriteToHOTPSlot::CommandTransaction::run(device, payload);
+ }
+
+ bool NitrokeyManager::write_TOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window,
+ bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
+ const char *temporary_password) {
+ if (!is_valid_totp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
+ int internal_slot_number = get_internal_slot_number_for_totp(slot_number);
+
+ if (is_authorization_command_supported()){
+ write_TOTP_slot_authorize(internal_slot_number, slot_name, secret, time_window, use_8_digits, use_enter, use_tokenID,
+ token_ID, temporary_password);
+ } else {
+ write_OTP_slot_no_authorize(internal_slot_number, slot_name, secret, time_window, use_8_digits, use_enter, use_tokenID,
+ token_ID, temporary_password);
+ }
+
+ return true;
+ }
+
+ void NitrokeyManager::write_OTP_slot_no_authorize(uint8_t internal_slot_number, const char *slot_name,
+ const char *secret,
+ uint64_t counter_or_interval, bool use_8_digits, bool use_enter,
+ bool use_tokenID, const char *token_ID,
+ const char *temporary_password) const {
+
+ auto payload2 = get_payload<stick10_08::SendOTPData>();
+ strcpyT(payload2.temporary_admin_password, temporary_password);
+ strcpyT(payload2.data, slot_name);
+ payload2.setTypeName();
+ stick10_08::SendOTPData::CommandTransaction::run(device, payload2);
+
+ payload2.setTypeSecret();
+ payload2.id = 0;
+ auto secret_bin = misc::hex_string_to_byte(secret);
+ auto remaining_secret_length = secret_bin.size();
+ const auto maximum_OTP_secret_size = 40;
+ if(remaining_secret_length > maximum_OTP_secret_size){
+ throw TargetBufferSmallerThanSource(remaining_secret_length, maximum_OTP_secret_size);
+ }
+
+ while (remaining_secret_length>0){
+ const auto bytesToCopy = std::min(sizeof(payload2.data), remaining_secret_length);
+ const auto start = secret_bin.size() - remaining_secret_length;
+ memset(payload2.data, 0, sizeof(payload2.data));
+ vector_copy_ranged(payload2.data, secret_bin, start, bytesToCopy);
+ stick10_08::SendOTPData::CommandTransaction::run(device, payload2);
+ remaining_secret_length -= bytesToCopy;
+ payload2.id++;
+ }
+
+ auto payload = get_payload<stick10_08::WriteToOTPSlot>();
+ strcpyT(payload.temporary_admin_password, temporary_password);
+ strcpyT(payload.slot_token_id, token_ID);
+ payload.use_8_digits = use_8_digits;
+ payload.use_enter = use_enter;
+ payload.use_tokenID = use_tokenID;
+ payload.slot_counter_or_interval = counter_or_interval;
+ payload.slot_number = internal_slot_number;
+ stick10_08::WriteToOTPSlot::CommandTransaction::run(device, payload);
+ }
+
+ void NitrokeyManager::write_TOTP_slot_authorize(uint8_t slot_number, const char *slot_name, const char *secret,
+ uint16_t time_window, bool use_8_digits, bool use_enter,
+ bool use_tokenID, const char *token_ID, const char *temporary_password) {
+ auto payload = get_payload<WriteToTOTPSlot>();
+ payload.slot_number = slot_number;
+ auto secret_bin = misc::hex_string_to_byte(secret);
+ vector_copy(payload.slot_secret, secret_bin);
+ strcpyT(payload.slot_name, slot_name);
+ strcpyT(payload.slot_token_id, token_ID);
+ payload.slot_interval = time_window; //FIXME naming
+ payload.use_8_digits = use_8_digits;
+ payload.use_enter = use_enter;
+ payload.use_tokenID = use_tokenID;
+
+ authorize_packet<WriteToTOTPSlot, Authorize>(payload, temporary_password, device);
+
+ auto resp = WriteToTOTPSlot::CommandTransaction::run(device, payload);
+ }
+
+ const char * NitrokeyManager::get_totp_slot_name(uint8_t slot_number) {
+ if (!is_valid_totp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
+ slot_number = get_internal_slot_number_for_totp(slot_number);
+ return get_slot_name(slot_number);
+ }
+ const char * NitrokeyManager::get_hotp_slot_name(uint8_t slot_number) {
+ if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
+ slot_number = get_internal_slot_number_for_hotp(slot_number);
+ return get_slot_name(slot_number);
+ }
+
+ static const int max_string_field_length = 2*1024; //storage's status string is ~1k
+
+ const char * NitrokeyManager::get_slot_name(uint8_t slot_number) {
+ auto payload = get_payload<GetSlotName>();
+ payload.slot_number = slot_number;
+ auto resp = GetSlotName::CommandTransaction::run(device, payload);
+ return strndup((const char *) resp.data().slot_name, max_string_field_length);
+ }
+
+ bool NitrokeyManager::first_authenticate(const char *pin, const char *temporary_password) {
+ auto authreq = get_payload<FirstAuthenticate>();
+ strcpyT(authreq.card_password, pin);
+ strcpyT(authreq.temporary_password, temporary_password);
+ FirstAuthenticate::CommandTransaction::run(device, authreq);
+ return true;
+ }
+
+ bool NitrokeyManager::set_time(uint64_t time) {
+ auto p = get_payload<SetTime>();
+ p.reset = 1;
+ p.time = time;
+ SetTime::CommandTransaction::run(device, p);
+ return false;
+ }
+
+ bool NitrokeyManager::get_time(uint64_t time) {
+ auto p = get_payload<SetTime>();
+ p.reset = 0;
+ p.time = time;
+ SetTime::CommandTransaction::run(device, p);
+ return true;
+ }
+
+ void NitrokeyManager::change_user_PIN(const char *current_PIN, const char *new_PIN) {
+ change_PIN_general<ChangeUserPin, PasswordKind::User>(current_PIN, new_PIN);
+ }
+
+ void NitrokeyManager::change_admin_PIN(const char *current_PIN, const char *new_PIN) {
+ change_PIN_general<ChangeAdminPin, PasswordKind::Admin>(current_PIN, new_PIN);
+ }
+
+ template <typename ProCommand, PasswordKind StoKind>
+ void NitrokeyManager::change_PIN_general(const char *current_PIN, const char *new_PIN) {
+ switch (device->get_device_model()){
+ case DeviceModel::PRO:
+ {
+ auto p = get_payload<ProCommand>();
+ strcpyT(p.old_pin, current_PIN);
+ strcpyT(p.new_pin, new_PIN);
+ ProCommand::CommandTransaction::run(device, p);
+ }
+ break;
+ //in Storage change admin/user pin is divided to two commands with 20 chars field len
+ case DeviceModel::STORAGE:
+ {
+ auto p = get_payload<ChangeAdminUserPin20Current>();
+ strcpyT(p.password, current_PIN);
+ p.set_kind(StoKind);
+ auto p2 = get_payload<ChangeAdminUserPin20New>();
+ strcpyT(p2.password, new_PIN);
+ p2.set_kind(StoKind);
+ ChangeAdminUserPin20Current::CommandTransaction::run(device, p);
+ ChangeAdminUserPin20New::CommandTransaction::run(device, p2);
+ }
+ break;
+ }
+
+ }
+
+ void NitrokeyManager::enable_password_safe(const char *user_pin) {
+ //The following command will cancel enabling PWS if it is not supported
+ auto a = get_payload<IsAESSupported>();
+ strcpyT(a.user_password, user_pin);
+ IsAESSupported::CommandTransaction::run(device, a);
+
+ auto p = get_payload<EnablePasswordSafe>();
+ strcpyT(p.user_password, user_pin);
+ EnablePasswordSafe::CommandTransaction::run(device, p);
+ }
+
+ vector <uint8_t> NitrokeyManager::get_password_safe_slot_status() {
+ auto responsePayload = GetPasswordSafeSlotStatus::CommandTransaction::run(device);
+ vector<uint8_t> v = vector<uint8_t>(responsePayload.data().password_safe_status,
+ responsePayload.data().password_safe_status
+ + sizeof(responsePayload.data().password_safe_status));
+ return v;
+ }
+
+ uint8_t NitrokeyManager::get_user_retry_count() {
+ if(device->get_device_model() == DeviceModel::STORAGE){
+ stick20::GetDeviceStatus::CommandTransaction::run(device);
+ }
+ auto response = GetUserPasswordRetryCount::CommandTransaction::run(device);
+ return response.data().password_retry_count;
+ }
+
+ uint8_t NitrokeyManager::get_admin_retry_count() {
+ if(device->get_device_model() == DeviceModel::STORAGE){
+ stick20::GetDeviceStatus::CommandTransaction::run(device);
+ }
+ auto response = GetPasswordRetryCount::CommandTransaction::run(device);
+ return response.data().password_retry_count;
+ }
+
+ void NitrokeyManager::lock_device() {
+ LockDevice::CommandTransaction::run(device);
+ }
+
+ const char *NitrokeyManager::get_password_safe_slot_name(uint8_t slot_number) {
+ if (!is_valid_password_safe_slot_number(slot_number)) throw InvalidSlotException(slot_number);
+ auto p = get_payload<GetPasswordSafeSlotName>();
+ p.slot_number = slot_number;
+ auto response = GetPasswordSafeSlotName::CommandTransaction::run(device, p);
+ return strndup((const char *) response.data().slot_name, max_string_field_length);
+ }
+
+ bool NitrokeyManager::is_valid_password_safe_slot_number(uint8_t slot_number) const { return slot_number < 16; }
+
+ const char *NitrokeyManager::get_password_safe_slot_login(uint8_t slot_number) {
+ if (!is_valid_password_safe_slot_number(slot_number)) throw InvalidSlotException(slot_number);
+ auto p = get_payload<GetPasswordSafeSlotLogin>();
+ p.slot_number = slot_number;
+ auto response = GetPasswordSafeSlotLogin::CommandTransaction::run(device, p);
+ return strndup((const char *) response.data().slot_login, max_string_field_length);
+ }
+
+ const char *NitrokeyManager::get_password_safe_slot_password(uint8_t slot_number) {
+ if (!is_valid_password_safe_slot_number(slot_number)) throw InvalidSlotException(slot_number);
+ auto p = get_payload<GetPasswordSafeSlotPassword>();
+ p.slot_number = slot_number;
+ auto response = GetPasswordSafeSlotPassword::CommandTransaction::run(device, p);
+ return strndup((const char *) response.data().slot_password, max_string_field_length); //FIXME use secure way
+ }
+
+ void NitrokeyManager::write_password_safe_slot(uint8_t slot_number, const char *slot_name, const char *slot_login,
+ const char *slot_password) {
+ if (!is_valid_password_safe_slot_number(slot_number)) throw InvalidSlotException(slot_number);
+ auto p = get_payload<SetPasswordSafeSlotData>();
+ p.slot_number = slot_number;
+ strcpyT(p.slot_name, slot_name);
+ strcpyT(p.slot_password, slot_password);
+ SetPasswordSafeSlotData::CommandTransaction::run(device, p);
+
+ auto p2 = get_payload<SetPasswordSafeSlotData2>();
+ p2.slot_number = slot_number;
+ strcpyT(p2.slot_login_name, slot_login);
+ SetPasswordSafeSlotData2::CommandTransaction::run(device, p2);
+ }
+
+ void NitrokeyManager::erase_password_safe_slot(uint8_t slot_number) {
+ if (!is_valid_password_safe_slot_number(slot_number)) throw InvalidSlotException(slot_number);
+ auto p = get_payload<ErasePasswordSafeSlot>();
+ p.slot_number = slot_number;
+ ErasePasswordSafeSlot::CommandTransaction::run(device, p);
+ }
+
+ void NitrokeyManager::user_authenticate(const char *user_password, const char *temporary_password) {
+ auto p = get_payload<UserAuthenticate>();
+ strcpyT(p.card_password, user_password);
+ strcpyT(p.temporary_password, temporary_password);
+ UserAuthenticate::CommandTransaction::run(device, p);
+ }
+
+ void NitrokeyManager::build_aes_key(const char *admin_password) {
+ switch (device->get_device_model()) {
+ case DeviceModel::PRO: {
+ auto p = get_payload<BuildAESKey>();
+ strcpyT(p.admin_password, admin_password);
+ BuildAESKey::CommandTransaction::run(device, p);
+ break;
+ }
+ case DeviceModel::STORAGE : {
+ auto p = get_payload<stick20::CreateNewKeys>();
+ strcpyT(p.password, admin_password);
+ p.set_defaults();
+ stick20::CreateNewKeys::CommandTransaction::run(device, p);
+ break;
+ }
+ }
+ }
+
+ void NitrokeyManager::factory_reset(const char *admin_password) {
+ auto p = get_payload<FactoryReset>();
+ strcpyT(p.admin_password, admin_password);
+ FactoryReset::CommandTransaction::run(device, p);
+ }
+
+ void NitrokeyManager::unlock_user_password(const char *admin_password, const char *new_user_password) {
+ switch (device->get_device_model()){
+ case DeviceModel::PRO: {
+ auto p = get_payload<stick10::UnlockUserPassword>();
+ strcpyT(p.admin_password, admin_password);
+ strcpyT(p.user_new_password, new_user_password);
+ stick10::UnlockUserPassword::CommandTransaction::run(device, p);
+ break;
+ }
+ case DeviceModel::STORAGE : {
+ auto p2 = get_payload<ChangeAdminUserPin20Current>();
+ p2.set_defaults();
+ strcpyT(p2.password, admin_password);
+ ChangeAdminUserPin20Current::CommandTransaction::run(device, p2);
+ auto p3 = get_payload<stick20::UnlockUserPin>();
+ p3.set_defaults();
+ strcpyT(p3.password, new_user_password);
+ stick20::UnlockUserPin::CommandTransaction::run(device, p3);
+ break;
+ }
+ }
+ }
+
+
+ void NitrokeyManager::write_config(uint8_t numlock, uint8_t capslock, uint8_t scrolllock, bool enable_user_password,
+ bool delete_user_password, const char *admin_temporary_password) {
+ auto p = get_payload<stick10_08::WriteGeneralConfig>();
+ p.numlock = (uint8_t) numlock;
+ p.capslock = (uint8_t) capslock;
+ p.scrolllock = (uint8_t) scrolllock;
+ p.enable_user_password = (uint8_t) enable_user_password;
+ p.delete_user_password = (uint8_t) delete_user_password;
+ if (is_authorization_command_supported()){
+ authorize_packet<stick10_08::WriteGeneralConfig, Authorize>(p, admin_temporary_password, device);
+ } else {
+ strcpyT(p.temporary_admin_password, admin_temporary_password);
+ }
+ stick10_08::WriteGeneralConfig::CommandTransaction::run(device, p);
+ }
+
+ vector<uint8_t> NitrokeyManager::read_config() {
+ auto responsePayload = GetStatus::CommandTransaction::run(device);
+ vector<uint8_t> v = vector<uint8_t>(responsePayload.data().general_config,
+ responsePayload.data().general_config+sizeof(responsePayload.data().general_config));
+ return v;
+ }
+
+ bool NitrokeyManager::is_authorization_command_supported(){
+ //authorization command is supported for versions equal or below:
+ auto m = std::unordered_map<DeviceModel , int, EnumClassHash>({
+ {DeviceModel::PRO, 7},
+ {DeviceModel::STORAGE, 999},
+ });
+ return get_minor_firmware_version() <= m[device->get_device_model()];
+ }
+
+ bool NitrokeyManager::is_320_OTP_secret_supported(){
+ //authorization command is supported for versions equal or below:
+ auto m = std::unordered_map<DeviceModel , int, EnumClassHash>({
+ {DeviceModel::PRO, 8},
+ {DeviceModel::STORAGE, 999},
+ });
+ return get_minor_firmware_version() >= m[device->get_device_model()];
+ }
+
+ DeviceModel NitrokeyManager::get_connected_device_model() const{
+ if (device == nullptr){
+ throw DeviceNotConnected("device not connected");
+ }
+ return device->get_device_model();
+ }
+
+ int NitrokeyManager::get_minor_firmware_version(){
+ switch(device->get_device_model()){
+ case DeviceModel::PRO:{
+ auto status_p = GetStatus::CommandTransaction::run(device);
+ return status_p.data().firmware_version; //7 or 8
+ }
+ case DeviceModel::STORAGE:{
+ auto status = stick20::GetDeviceStatus::CommandTransaction::run(device);
+ return status.data().versionInfo.minor;
+ }
+ }
+ return 0;
+ }
+
+ bool NitrokeyManager::is_AES_supported(const char *user_password) {
+ auto a = get_payload<IsAESSupported>();
+ strcpyT(a.user_password, user_password);
+ IsAESSupported::CommandTransaction::run(device, a);
+ return true;
+ }
+
+ //storage commands
+
+ void NitrokeyManager::send_startup(uint64_t seconds_from_epoch){
+ auto p = get_payload<stick20::SendStartup>();
+// p.set_defaults(); //set current time
+ p.localtime = seconds_from_epoch;
+ stick20::SendStartup::CommandTransaction::run(device, p);
+ }
+
+ void NitrokeyManager::unlock_encrypted_volume(const char* user_pin){
+ misc::execute_password_command<stick20::EnableEncryptedPartition>(device, user_pin);
+ }
+
+ void NitrokeyManager::unlock_hidden_volume(const char* hidden_volume_password) {
+ misc::execute_password_command<stick20::EnableHiddenEncryptedPartition>(device, hidden_volume_password);
+ }
+
+ //TODO check is encrypted volume unlocked before execution
+ //if not return library exception
+ void NitrokeyManager::create_hidden_volume(uint8_t slot_nr, uint8_t start_percent, uint8_t end_percent,
+ const char *hidden_volume_password) {
+ auto p = get_payload<stick20::SetupHiddenVolume>();
+ p.SlotNr_u8 = slot_nr;
+ p.StartBlockPercent_u8 = start_percent;
+ p.EndBlockPercent_u8 = end_percent;
+ strcpyT(p.HiddenVolumePassword_au8, hidden_volume_password);
+ stick20::SetupHiddenVolume::CommandTransaction::run(device, p);
+ }
+
+ void NitrokeyManager::set_unencrypted_read_only(const char* user_pin) {
+ misc::execute_password_command<stick20::SendSetReadonlyToUncryptedVolume>(device, user_pin);
+ }
+
+ void NitrokeyManager::set_unencrypted_read_write(const char* user_pin) {
+ misc::execute_password_command<stick20::SendSetReadwriteToUncryptedVolume>(device, user_pin);
+ }
+
+ void NitrokeyManager::export_firmware(const char* admin_pin) {
+ misc::execute_password_command<stick20::ExportFirmware>(device, admin_pin);
+ }
+
+ void NitrokeyManager::enable_firmware_update(const char* firmware_pin) {
+ misc::execute_password_command<stick20::EnableFirmwareUpdate>(device, firmware_pin);
+ }
+
+ void NitrokeyManager::clear_new_sd_card_warning(const char* admin_pin) {
+ misc::execute_password_command<stick20::SendClearNewSdCardFound>(device, admin_pin);
+ }
+
+ void NitrokeyManager::fill_SD_card_with_random_data(const char* admin_pin) {
+ auto p = get_payload<stick20::FillSDCardWithRandomChars>();
+ p.set_defaults();
+ strcpyT(p.admin_pin, admin_pin);
+ stick20::FillSDCardWithRandomChars::CommandTransaction::run(device, p);
+ }
+
+ void NitrokeyManager::change_update_password(const char* current_update_password, const char* new_update_password) {
+ auto p = get_payload<stick20::ChangeUpdatePassword>();
+ strcpyT(p.current_update_password, current_update_password);
+ strcpyT(p.new_update_password, new_update_password);
+ stick20::ChangeUpdatePassword::CommandTransaction::run(device, p);
+ }
+
+ const char * NitrokeyManager::get_status_storage_as_string(){
+ auto p = stick20::GetDeviceStatus::CommandTransaction::run(device);
+ return strndup(p.data().dissect().c_str(), max_string_field_length);
+ }
+
+ stick20::DeviceConfigurationResponsePacket::ResponsePayload NitrokeyManager::get_status_storage(){
+ auto p = stick20::GetDeviceStatus::CommandTransaction::run(device);
+ return p.data();
+ }
+
+ const char * NitrokeyManager::get_SD_usage_data_as_string(){
+ auto p = stick20::GetSDCardOccupancy::CommandTransaction::run(device);
+ return strndup(p.data().dissect().c_str(), max_string_field_length);
+ }
+
+ std::pair<uint8_t,uint8_t> NitrokeyManager::get_SD_usage_data(){
+ auto p = stick20::GetSDCardOccupancy::CommandTransaction::run(device);
+ return std::make_pair(p.data().WriteLevelMin, p.data().WriteLevelMax);
+ }
+
+ int NitrokeyManager::get_progress_bar_value(){
+ try{
+ stick20::GetDeviceStatus::CommandTransaction::run(device);
+ return -1;
+ }
+ catch (LongOperationInProgressException &e){
+ return e.progress_bar_value;
+ }
+ }
+
+ string NitrokeyManager::get_TOTP_code(uint8_t slot_number, const char *user_temporary_password) {
+ return get_TOTP_code(slot_number, 0, 0, 0, user_temporary_password);
+ }
+
+ stick10::ReadSlot::ResponsePayload NitrokeyManager::get_OTP_slot_data(const uint8_t slot_number) {
+ auto p = get_payload<stick10::ReadSlot>();
+ p.slot_number = slot_number;
+ auto data = stick10::ReadSlot::CommandTransaction::run(device, p);
+ return data.data();
+ }
+
+ stick10::ReadSlot::ResponsePayload NitrokeyManager::get_TOTP_slot_data(const uint8_t slot_number) {
+ return get_OTP_slot_data(get_internal_slot_number_for_totp(slot_number));
+ }
+
+ stick10::ReadSlot::ResponsePayload NitrokeyManager::get_HOTP_slot_data(const uint8_t slot_number) {
+ auto slot_data = get_OTP_slot_data(get_internal_slot_number_for_hotp(slot_number));
+ if (device->get_device_model() == DeviceModel::STORAGE){
+ //convert counter from string to ull
+ auto counter_s = std::string(slot_data.slot_counter_s, slot_data.slot_counter_s+sizeof(slot_data.slot_counter_s));
+ slot_data.slot_counter = std::stoull(counter_s);
+ }
+ return slot_data;
+ }
+
+ void NitrokeyManager::lock_encrypted_volume() {
+ misc::execute_password_command<stick20::DisableEncryptedPartition>(device, "");
+ }
+
+ void NitrokeyManager::lock_hidden_volume() {
+ misc::execute_password_command<stick20::DisableHiddenEncryptedPartition>(device, "");
+ }
+
+ uint8_t NitrokeyManager::get_SD_card_size() {
+ auto data = stick20::ProductionTest::CommandTransaction::run(device);
+ return data.data().SD_Card_Size_u8;
+ }
+
+
+}
diff --git a/README.md b/README.md
index 942053f..2848cc2 100644
--- a/README.md
+++ b/README.md
@@ -1,22 +1,197 @@
-![catch logo](catch-logo-small.png)
+[![Stories in Ready](https://badge.waffle.io/Nitrokey/libnitrokey.png?label=ready&title=Ready)](https://waffle.io/Nitrokey/libnitrokey)
+[![Build Status](https://travis-ci.org/Nitrokey/libnitrokey.svg?branch=master)](https://travis-ci.org/Nitrokey/libnitrokey)
+# 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).
-*v1.3.5*
+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).
-Build status (on Travis CI) [![Build Status](https://travis-ci.org/philsquared/Catch.png)](https://travis-ci.org/philsquared/Catch)
+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).
-<a href="https://raw.githubusercontent.com/philsquared/Catch/master/single_include/catch.hpp">The latest, single header, version can be downloaded directly using this link</a>
+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 6.4 environment (OSX 10.10).
-## What's the Catch?
+## Getting sources
+This repository uses `git submodules`.
+To clone please use git's `--recursive` option like in:
+```bash
+git clone --recursive https://github.com/Nitrokey/libnitrokey.git
+```
+or for already cloned repository:
+```bash
+git clone https://github.com/Nitrokey/libnitrokey.git
+cd libnitrokey
+git submodule update --init --recursive
+```
-Catch stands for C++ Automated Test Cases in Headers and is a multi-paradigm automated test framework for C++ and Objective-C (and, maybe, C). It is implemented entirely in a set of header files, but is packaged up as a single header for extra convenience.
+## Dependencies
+Following libraries are needed to use libnitrokey on Linux (names of the packages on Ubuntu):
+- libhidapi-dev [(HID API)](http://www.signal11.us/oss/hidapi/)
+- libusb-1.0-0-dev
-## How to use it
-This documentation comprises these three parts:
-* [Why do we need yet another C++ Test Framework?](docs/why-catch.md)
-* [Tutorial](docs/tutorial.md) - getting started
-* [Reference section](docs/Readme.md) - all the details
+## Compilation
+libnitrokey uses CMake as its main build system. As a secondary option it offers building through Qt's qMake.
+### Qt
+A .pro project file is provided for direct compilation and for inclusion to other projects.
-## More
-* Issues and bugs can be raised on the [Issue tracker on GitHub](https://github.com/philsquared/Catch/issues)
-* For discussion or questions please use [the dedicated Google Groups forum](https://groups.google.com/forum/?fromgroups#!forum/catch-forum)
+### Windows MS Visual Studio 2017
+Lately Visual Studio has started handling CMake files directly. After opening the project's directory it should recognize it and initialize build system. Afterwards please run:
+1. `CMake -> Cache -> View Cache CMakeLists.txt -> CMakeLists.txt` to edit settings
+2. `CMake -> Build All` to build
+
+It is possible too to use CMake GUI directly with its settings editor.
+
+### Linux CLI
+To compile please run following sequence of commands:
+```bash
+# assuming current dir is ./libnitrokey/
+mkdir -p build
+cd build
+cmake .. <OPTIONS>
+make -j2
+```
+
+By default (with empty `<OPTIONS>` string) this will create two static library files - build/libnitrokey-static.a and build/libnitrokey-static-log.a. If you wish to build another version (e.g. shared library to use with Python) you can use as `<OPTIONS>` string `-DLIBNITROKEY_STATIC=OFF`. All options could be listed with `cmake .. -L` or instead `cmake` a `ccmake ..` tool could be used for configuration (where `..` is the path with `CMakeLists.txt` file). `ccmake` shows also description of the build parameters.
+
+If you have trouble compiling or running the library you can check [.travis.yml](.travis.yml) file for configuration details. This file is used by our CI service to make test builds on OSX and Ubuntu 14.04.
+
+Other build options (all take either `ON` or `OFF`):
+* ADD_ASAN - add tests for memory leaks and out-of-bounds access
+* ADD_TSAN - add tests for threads race, needs USE_CLANG
+* USE_CLANG - forces Clang as the compiler
+* COMPILE_TESTS - compile C++ tests
+
+
+
+# Using libnitrokey with Python
+To use libnitrokey with Python a [CFFI](http://cffi.readthedocs.io/en/latest/overview.html) library is required (either 2.7+ or 3.0+). It can be installed with:
+```bash
+pip install --user cffi # for python 2.x
+pip3 install cffi # for python 3.x
+```
+Just import it, read the C API header and it is done! You have access to the library. Here is an example printing HOTP code for Pro or Storage device, assuming it is run in root directory [(full example)](python_bindings_example.py):
+```python
+#!/usr/bin/env python
+import cffi
+
+ffi = cffi.FFI()
+get_string = ffi.string
+
+
+def get_library():
+ fp = 'NK_C_API.h' # path to C API header
+
+ declarations = []
+ with open(fp, 'r') as f:
+ declarations = f.readlines()
+
+ a = iter(declarations)
+ for declaration in a:
+ if declaration.startswith('NK_C_API'):
+ declaration = declaration.replace('NK_C_API', '').strip()
+ while not ';' in declaration:
+ declaration += (next(a)).strip()
+ print(declaration)
+ ffi.cdef(declaration, override=True)
+
+ C = None
+ import os, sys
+ path_build = os.path.join(".", "build")
+ paths = [ os.path.join(path_build,"libnitrokey-log.so"),
+ os.path.join(path_build,"libnitrokey.so")]
+ for p in paths:
+ print p
+ if os.path.exists(p):
+ C = ffi.dlopen(p)
+ break
+ else:
+ print("File does not exist: " + p)
+ print("Trying another")
+ if not C:
+ print("No library file found")
+ sys.exit(1)
+
+ C.NK_set_debug(False)
+ nk_login = C.NK_login_auto() # try to connect firstly to Pro and then to Storage
+ 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_major_firmware_version()
+ model = 'P' if firmware_version in [7,8] else 'S'
+ device_type = (model, firmware_version)
+
+ C.NK_set_debug(True)
+
+ return C
+
+
+def get_hotp_code(lib, i):
+ return lib.NK_get_hotp_code(i)
+
+
+libnitrokey = get_library()
+libnitrokey.NK_set_debug(False) # do not show debug messages (log library only)
+
+hotp_slot_code = get_hotp_code(libnitrokey, 1)
+print('Getting HOTP code from Nitrokey device: ')
+print(hotp_slot_code)
+libnitrokey.NK_logout() # disconnect device
+```
+
+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.
+
+## Documentation
+The documentation of C API is included in the sources (could be generated with doxygen if requested).
+Please check NK_C_API.h (C API) for high level commands and include/NitrokeyManager.h (C++ API). All devices' commands are listed along with packet format in include/stick10_commands.h and include/stick20_commands.h respectively for Nitrokey Pro and Nitrokey Storage products.
+
+# Tests
+Warning! Before you run unittests please either change both your Admin and User PINs on your Nitrostick to defaults (`12345678` and `123456` respectively) or change the values in tests source code. If you do not change them the tests might lock your device. If it's too late, you can always reset your Nitrokey using instructions from [homepage](https://www.nitrokey.com/de/documentation/how-reset-nitrokey).
+
+## Python tests
+Libnitrokey has a great suite of tests written in Python 3 under the path: `unittest/test_*.py`:
+* `test_pro.py` - contains tests of OTP, Password Safe and PIN control functionality. Could be run on both Pro and Storage devices.
+* `test_storage.py` - contains tests of Encrypted Volumes functionality. Could be run only on Storage.
+The tests themselves show how to handle common requests to device.
+Before running please install all required libraries with:
+```bash
+cd unittest
+pip install --user -r requirements.txt
+```
+To run them please execute:
+```bash
+# substitute <dev> with either pro or storage
+py.test -v test_<dev>.py
+# more specific use - run tests containing in name <test_name> 5 times:
+py.test -v test_<dev>.py -k <test_name> --count 5
+
+```
+For additional documentation please check the following for [py.test installation](http://doc.pytest.org/en/latest/getting-started.html). For better coverage [randomly plugin](https://pypi.python.org/pypi/pytest-randomly) is installed - it randomizes the test order allowing to detect unseen dependencies between the tests.
+
+## C++ tests
+There are also some unit tests implemented in C++, placed in unittest directory. They are not written as extensively as Python tests and are rather more a C++ low level interface check, often not using C++ API from NitrokeyManager.cc. Some of them are: [test_HOTP.cc](https://github.com/Nitrokey/libnitrokey/blob/master/unittest/test_HOTP.cc)
+[test.cc](https://github.com/Nitrokey/libnitrokey/blob/master/unittest/test.cc)
+Unit tests were written and tested on Ubuntu 16.04/16.10. To run them just execute binaries built in ./libnitrokey/build dir after enabling them by passing `-DCOMPILE_TESTS` option like in `cmake .. -DCOMPILE_TESTS && make`.
+
+
+The documentation of how it works could be found in nitrokey-app project's README on Github:
+[Nitrokey-app - internals](https://github.com/Nitrokey/nitrokey-app/blob/master/README.md#internals).
+
+To peek/debug communication with device running nitrokey-app (0.x branch) in debug mode (`-d` switch) and checking the logs
+(right click on tray icon and then 'Debug') might be helpful. Latest Nitrokey App (1.x branch) uses libnitrokey to communicate with device. Once linked with `libnitrokey-log` it will print all communication to the console. Additionally crosschecking with
+firmware code should show how things works:
+[report_protocol.c](https://github.com/Nitrokey/nitrokey-pro-firmware/blob/master/src/keyboard/report_protocol.c)
+(for Nitrokey Pro, for Storage similarly).
+
+# Known issues / tasks
+* Currently only one device can be connected at a time
+* C++ API needs some reorganization to C++ objects (instead of pointers to arrays). This will be also preparing for integration with Pybind11,
+* Fix compilation warnings
+
+Other tasks might be listed either in [TODO](TODO) file or on project's issues page.
+
+# License
+This project is licensed under LGPL version 3. License text could be found under [LICENSE](LICENSE) file.
+
+# Roadmap
+To check what issues will be fixed and when please check [milestones](https://github.com/Nitrokey/libnitrokey/milestones) page.
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..3183a43
--- /dev/null
+++ b/TODO
@@ -0,0 +1 @@
+use strings instead of char* and vectors instead of others
diff --git a/build/.gitignore b/build/.gitignore
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/build/.gitignore
@@ -0,0 +1 @@
+*
diff --git a/command_id.cc b/command_id.cc
new file mode 100644
index 0000000..f76a358
--- /dev/null
+++ b/command_id.cc
@@ -0,0 +1,152 @@
+#include <assert.h>
+#include "command_id.h"
+
+namespace nitrokey {
+namespace proto {
+
+const char *commandid_to_string(CommandID id) {
+ switch (id) {
+ case CommandID::GET_STATUS:
+ return "GET_STATUS";
+ case CommandID::WRITE_TO_SLOT:
+ return "WRITE_TO_SLOT";
+ case CommandID::READ_SLOT_NAME:
+ return "READ_SLOT_NAME";
+ case CommandID::READ_SLOT:
+ return "READ_SLOT";
+ case CommandID::GET_CODE:
+ return "GET_CODE";
+ case CommandID::WRITE_CONFIG:
+ return "WRITE_CONFIG";
+ case CommandID::ERASE_SLOT:
+ return "ERASE_SLOT";
+ case CommandID::FIRST_AUTHENTICATE:
+ return "FIRST_AUTHENTICATE";
+ case CommandID::AUTHORIZE:
+ return "AUTHORIZE";
+ case CommandID::GET_PASSWORD_RETRY_COUNT:
+ return "GET_PASSWORD_RETRY_COUNT";
+ case CommandID::CLEAR_WARNING:
+ return "CLEAR_WARNING";
+ case CommandID::SET_TIME:
+ return "SET_TIME";
+ case CommandID::TEST_COUNTER:
+ return "TEST_COUNTER";
+ case CommandID::TEST_TIME:
+ return "TEST_TIME";
+ case CommandID::USER_AUTHENTICATE:
+ return "USER_AUTHENTICATE";
+ case CommandID::GET_USER_PASSWORD_RETRY_COUNT:
+ return "GET_USER_PASSWORD_RETRY_COUNT";
+ case CommandID::USER_AUTHORIZE:
+ return "USER_AUTHORIZE";
+ case CommandID::UNLOCK_USER_PASSWORD:
+ return "UNLOCK_USER_PASSWORD";
+ case CommandID::LOCK_DEVICE:
+ return "LOCK_DEVICE";
+ case CommandID::FACTORY_RESET:
+ return "FACTORY_RESET";
+ case CommandID::CHANGE_USER_PIN:
+ return "CHANGE_USER_PIN";
+ case CommandID::CHANGE_ADMIN_PIN:
+ return "CHANGE_ADMIN_PIN";
+
+ case CommandID::ENABLE_CRYPTED_PARI:
+ return "ENABLE_CRYPTED_PARI";
+ case CommandID::DISABLE_CRYPTED_PARI:
+ return "DISABLE_CRYPTED_PARI";
+ case CommandID::ENABLE_HIDDEN_CRYPTED_PARI:
+ return "ENABLE_HIDDEN_CRYPTED_PARI";
+ case CommandID::DISABLE_HIDDEN_CRYPTED_PARI:
+ return "DISABLE_HIDDEN_CRYPTED_PARI";
+ case CommandID::ENABLE_FIRMWARE_UPDATE:
+ return "ENABLE_FIRMWARE_UPDATE";
+ case CommandID::EXPORT_FIRMWARE_TO_FILE:
+ return "EXPORT_FIRMWARE_TO_FILE";
+ case CommandID::GENERATE_NEW_KEYS:
+ return "GENERATE_NEW_KEYS";
+ case CommandID::FILL_SD_CARD_WITH_RANDOM_CHARS:
+ return "FILL_SD_CARD_WITH_RANDOM_CHARS";
+
+ case CommandID::WRITE_STATUS_DATA:
+ return "WRITE_STATUS_DATA";
+ case CommandID::ENABLE_READONLY_UNCRYPTED_LUN:
+ return "ENABLE_READONLY_UNCRYPTED_LUN";
+ case CommandID::ENABLE_READWRITE_UNCRYPTED_LUN:
+ return "ENABLE_READWRITE_UNCRYPTED_LUN";
+
+ case CommandID::SEND_PASSWORD_MATRIX:
+ return "SEND_PASSWORD_MATRIX";
+ case CommandID::SEND_PASSWORD_MATRIX_PINDATA:
+ return "SEND_PASSWORD_MATRIX_PINDATA";
+ case CommandID::SEND_PASSWORD_MATRIX_SETUP:
+ return "SEND_PASSWORD_MATRIX_SETUP";
+
+ case CommandID::GET_DEVICE_STATUS:
+ return "GET_DEVICE_STATUS";
+ case CommandID::SEND_DEVICE_STATUS:
+ return "SEND_DEVICE_STATUS";
+
+ case CommandID::SEND_HIDDEN_VOLUME_PASSWORD:
+ return "SEND_HIDDEN_VOLUME_PASSWORD";
+ case CommandID::SEND_HIDDEN_VOLUME_SETUP:
+ return "SEND_HIDDEN_VOLUME_SETUP";
+ case CommandID::SEND_PASSWORD:
+ return "SEND_PASSWORD";
+ case CommandID::SEND_NEW_PASSWORD:
+ return "SEND_NEW_PASSWORD";
+ case CommandID::CLEAR_NEW_SD_CARD_FOUND:
+ return "CLEAR_NEW_SD_CARD_FOUND";
+
+ case CommandID::SEND_STARTUP:
+ return "SEND_STARTUP";
+ case CommandID::SEND_CLEAR_STICK_KEYS_NOT_INITIATED:
+ return "SEND_CLEAR_STICK_KEYS_NOT_INITIATED";
+ case CommandID::SEND_LOCK_STICK_HARDWARE:
+ return "SEND_LOCK_STICK_HARDWARE";
+
+ case CommandID::PRODUCTION_TEST:
+ return "PRODUCTION_TEST";
+ case CommandID::SEND_DEBUG_DATA:
+ return "SEND_DEBUG_DATA";
+
+ case CommandID::CHANGE_UPDATE_PIN:
+ return "CHANGE_UPDATE_PIN";
+
+ case CommandID::GET_PW_SAFE_SLOT_STATUS:
+ return "GET_PW_SAFE_SLOT_STATUS";
+ case CommandID::GET_PW_SAFE_SLOT_NAME:
+ return "GET_PW_SAFE_SLOT_NAME";
+ case CommandID::GET_PW_SAFE_SLOT_PASSWORD:
+ return "GET_PW_SAFE_SLOT_PASSWORD";
+ case CommandID::GET_PW_SAFE_SLOT_LOGINNAME:
+ return "GET_PW_SAFE_SLOT_LOGINNAME";
+ case CommandID::SET_PW_SAFE_SLOT_DATA_1:
+ return "SET_PW_SAFE_SLOT_DATA_1";
+ case CommandID::SET_PW_SAFE_SLOT_DATA_2:
+ return "SET_PW_SAFE_SLOT_DATA_2";
+ case CommandID::PW_SAFE_ERASE_SLOT:
+ return "PW_SAFE_ERASE_SLOT";
+ case CommandID::PW_SAFE_ENABLE:
+ return "PW_SAFE_ENABLE";
+ case CommandID::PW_SAFE_INIT_KEY:
+ return "PW_SAFE_INIT_KEY";
+ case CommandID::PW_SAFE_SEND_DATA:
+ return "PW_SAFE_SEND_DATA";
+ case CommandID::SD_CARD_HIGH_WATERMARK:
+ return "SD_CARD_HIGH_WATERMARK";
+ case CommandID::DETECT_SC_AES:
+ return "DETECT_SC_AES";
+ case CommandID::NEW_AES_KEY:
+ return "NEW_AES_KEY";
+ case CommandID::WRITE_TO_SLOT_2:
+ return "WRITE_TO_SLOT_2";
+ break;
+ case CommandID::SEND_OTP_DATA:
+ return "SEND_OTP_DATA";
+ break;
+ }
+ return "UNKNOWN";
+}
+}
+}
diff --git a/device.cc b/device.cc
new file mode 100644
index 0000000..8ec05eb
--- /dev/null
+++ b/device.cc
@@ -0,0 +1,245 @@
+#include <chrono>
+#include <thread>
+#include <cstddef>
+#include <stdexcept>
+#include "hidapi/hidapi.h"
+#include "include/misc.h"
+#include "include/device.h"
+#include "include/log.h"
+#include <mutex>
+#include "DeviceCommunicationExceptions.h"
+#include "device.h"
+
+std::mutex mex_dev_com;
+
+using namespace nitrokey::device;
+using namespace nitrokey::log;
+using namespace std::chrono;
+
+std::atomic_int Device::instances_count{0};
+std::chrono::milliseconds Device::default_delay {0} ;
+
+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)
+ :
+ last_command_status(0),
+ m_vid(vid),
+ m_pid(pid),
+ m_model(model),
+ m_retry_sending_count(1),
+ m_retry_receiving_count(retry_receiving_count),
+ m_retry_timeout(retry_timeout),
+ m_send_receive_delay(send_receive_delay),
+ mp_devhandle(nullptr)
+{
+ instances_count++;
+}
+
+bool Device::disconnect() {
+ //called in object's destructor
+ LOG(__FUNCTION__, Loglevel::DEBUG_L2);
+ std::lock_guard<std::mutex> lock(mex_dev_com);
+ return _disconnect();
+}
+
+bool Device::_disconnect() {
+ LOG(std::string(__FUNCTION__) + std::string(m_model == DeviceModel::PRO ? "PRO" : "STORAGE"), Loglevel::DEBUG_L2);
+ LOG(std::string(__FUNCTION__) + std::string(" *IN* "), Loglevel::DEBUG_L2);
+
+ LOG(std::string("Disconnection success: ") + std::to_string(mp_devhandle == nullptr), Loglevel::DEBUG_L2);
+ if(mp_devhandle == nullptr) return false;
+
+ hid_close(mp_devhandle);
+ mp_devhandle = nullptr;
+#ifndef __APPLE__
+ if (instances_count == 1){
+ LOG(std::string("Calling hid_exit"), Loglevel::DEBUG_L2);
+ hid_exit();
+ }
+#endif
+ return true;
+}
+
+bool Device::connect() {
+ LOG(__FUNCTION__, Loglevel::DEBUG_L2);
+ std::lock_guard<std::mutex> lock(mex_dev_com);
+ return _connect();
+}
+
+bool Device::_connect() {
+ LOG(std::string(__FUNCTION__) + std::string(" *IN* "), Loglevel::DEBUG_L2);
+
+// hid_init(); // done automatically on hid_open
+ mp_devhandle = hid_open(m_vid, m_pid, nullptr);
+ const bool success = mp_devhandle != nullptr;
+ LOG(std::string("Connection success: ") + std::to_string(success), Loglevel::DEBUG_L2);
+ return success;
+}
+
+int Device::send(const void *packet) {
+ LOG(__FUNCTION__, Loglevel::DEBUG_L2);
+ std::lock_guard<std::mutex> lock(mex_dev_com);
+ LOG(std::string(__FUNCTION__) + std::string(" *IN* "), Loglevel::DEBUG_L2);
+
+ int send_feature_report = -1;
+
+ for (int i = 0; i < 3 && send_feature_report < 0; ++i) {
+ if (mp_devhandle == nullptr) {
+ LOG(std::string("Connection fail") , Loglevel::DEBUG_L2);
+ throw DeviceNotConnected("Attempted HID send on an invalid descriptor.");
+ }
+ send_feature_report = hid_send_feature_report(
+ mp_devhandle, (const unsigned char *)(packet), HID_REPORT_SIZE);
+ if (send_feature_report < 0) _reconnect();
+ //add thread sleep?
+ LOG(std::string("Sending attempt: ")+std::to_string(i+1) + " / 3" , Loglevel::DEBUG_L2);
+ }
+ return send_feature_report;
+}
+
+int Device::recv(void *packet) {
+ LOG(__FUNCTION__, Loglevel::DEBUG_L2);
+ std::lock_guard<std::mutex> lock(mex_dev_com);
+ LOG(std::string(__FUNCTION__) + std::string(" *IN* "), Loglevel::DEBUG_L2);
+ int status;
+ int retry_count = 0;
+
+ for (;;) {
+ if (mp_devhandle == nullptr){
+ LOG(std::string("Connection fail") , Loglevel::DEBUG_L2);
+ throw DeviceNotConnected("Attempted HID receive on an invalid descriptor.");
+ }
+
+ status = (hid_get_feature_report(mp_devhandle, (unsigned char *)(packet),
+ HID_REPORT_SIZE));
+
+ auto pwherr = hid_error(mp_devhandle);
+ std::wstring wherr = (pwherr != nullptr) ? pwherr : L"No error message";
+ std::string herr(wherr.begin(), wherr.end());
+ LOG(std::string("libhid error message: ") + herr,
+ Loglevel::DEBUG_L2);
+
+ if (status > 0) break; // success
+ if (retry_count++ >= m_retry_receiving_count) {
+ LOG(
+ "Maximum retry count reached: " + std::to_string(retry_count),
+ Loglevel::WARNING);
+ LOG(
+ std::string("Counter stats: ") + m_counters.get_as_string(),
+ Loglevel::DEBUG);
+ break;
+ }
+ _reconnect();
+ LOG("Retrying... " + std::to_string(retry_count),
+ Loglevel::DEBUG);
+ std::this_thread::sleep_for(m_retry_timeout);
+ }
+
+ return status;
+}
+
+bool Device::could_be_enumerated() {
+ LOG(__FUNCTION__, Loglevel::DEBUG_L2);
+ std::lock_guard<std::mutex> lock(mex_dev_com);
+ if (mp_devhandle==nullptr){
+ return false;
+ }
+#ifndef __APPLE__
+ auto pInfo = hid_enumerate(m_vid, m_pid);
+ if (pInfo != nullptr){
+ hid_free_enumeration(pInfo);
+ return true;
+ }
+ return false;
+#else
+// alternative for OSX
+ unsigned char buf[1];
+ return hid_read_timeout(mp_devhandle, buf, sizeof(buf), 20) != -1;
+#endif
+}
+
+void Device::show_stats() {
+ auto s = m_counters.get_as_string();
+ LOG(s, Loglevel::DEBUG_L2);
+}
+
+void Device::_reconnect() {
+ LOG(__FUNCTION__, Loglevel::DEBUG_L2);
+ ++m_counters.low_level_reconnect;
+ _disconnect();
+ _connect();
+}
+
+Device::~Device() {
+ show_stats();
+ disconnect();
+ instances_count--;
+}
+
+void Device::set_default_device_speed(int delay) {
+ default_delay = std::chrono::duration<int, std::milli>(delay);
+}
+
+
+void Device::set_receiving_delay(const std::chrono::milliseconds delay){
+ std::lock_guard<std::mutex> lock(mex_dev_com);
+ m_send_receive_delay = delay;
+}
+
+void Device::set_retry_delay(const std::chrono::milliseconds delay){
+ std::lock_guard<std::mutex> lock(mex_dev_com);
+ m_retry_timeout = delay;
+}
+
+Stick10::Stick10():
+ Device(0x20a0, 0x4108, DeviceModel::PRO, 100ms, 5, 100ms)
+ {
+ setDefaultDelay();
+ }
+
+
+Stick20::Stick20():
+ Device(0x20a0, 0x4109, DeviceModel::STORAGE, 40ms, 25, 40ms)
+ {
+ setDefaultDelay();
+ }
+
+#include <sstream>
+#define p(x) ss << #x << " " << x << ", ";
+std::string Device::ErrorCounters::get_as_string() {
+ std::stringstream ss;
+ p(total_comm_runs);
+ p(communication_successful);
+ ss << "(";
+ p(command_successful_recv);
+ p(command_result_not_equal_0_recv);
+ ss << "), ";
+ p(sends_executed);
+ p(recv_executed);
+ p(successful_storage_commands);
+ p(total_retries);
+ ss << "(";
+ p(busy);
+ p(busy_progressbar);
+ p(CRC_other_than_awaited);
+ p(wrong_CRC);
+ ss << "), ";
+ p(low_level_reconnect);
+ p(sending_error);
+ p(receiving_error);
+ return ss.str();
+}
+
+void Device::setDefaultDelay() {
+ LOG(__FUNCTION__, Loglevel::DEBUG_L2);
+
+ auto count = default_delay.count();
+ if (count != 0){
+ LOG("Setting default delay to " + std::to_string(count), Loglevel::DEBUG_L2);
+ m_retry_timeout = default_delay;
+ m_send_receive_delay = default_delay;
+ }
+}
+
+#undef p
diff --git a/hidapi b/hidapi
new file mode 160000
+Subproject b767b43f3e6f9c5b92ea7d738331deb8e03c4ba
diff --git a/include/CommandFailedException.h b/include/CommandFailedException.h
new file mode 100644
index 0000000..417e850
--- /dev/null
+++ b/include/CommandFailedException.h
@@ -0,0 +1,54 @@
+//
+// Created by sz on 23.07.16.
+//
+
+#ifndef LIBNITROKEY_COMMANDFAILEDEXCEPTION_H
+#define LIBNITROKEY_COMMANDFAILEDEXCEPTION_H
+
+#include <exception>
+#include <cstdint>
+#include "log.h"
+#include "command_id.h"
+
+using cs = nitrokey::proto::stick10::command_status;
+
+class CommandFailedException : public std::exception {
+public:
+ const uint8_t last_command_id;
+ const uint8_t last_command_status;
+
+ CommandFailedException(uint8_t last_command_id, uint8_t last_command_status) :
+ last_command_id(last_command_id),
+ last_command_status(last_command_status){
+ LOG(std::string("CommandFailedException, status: ")+ std::to_string(last_command_status), nitrokey::log::Loglevel::DEBUG);
+ }
+
+ virtual const char *what() const throw() {
+ return "Command execution has failed on device";
+ }
+
+
+ bool reason_timestamp_warning() const throw(){
+ return last_command_status == static_cast<uint8_t>(cs::timestamp_warning);
+ }
+
+ bool reason_AES_not_initialized() const throw(){
+ return last_command_status == static_cast<uint8_t>(cs::AES_dec_failed);
+ }
+
+ bool reason_not_authorized() const throw(){
+ return last_command_status == static_cast<uint8_t>(cs::not_authorized);
+ }
+
+ bool reason_slot_not_programmed() const throw(){
+ return last_command_status == static_cast<uint8_t>(cs::slot_not_programmed);
+ }
+
+ bool reason_wrong_password() const throw(){
+ return last_command_status == static_cast<uint8_t>(cs::wrong_password);
+ }
+
+};
+
+
+#endif //LIBNITROKEY_COMMANDFAILEDEXCEPTION_H
diff --git a/include/DeviceCommunicationExceptions.h b/include/DeviceCommunicationExceptions.h
new file mode 100644
index 0000000..1c77f5b
--- /dev/null
+++ b/include/DeviceCommunicationExceptions.h
@@ -0,0 +1,51 @@
+#ifndef LIBNITROKEY_DEVICECOMMUNICATIONEXCEPTIONS_H
+#define LIBNITROKEY_DEVICECOMMUNICATIONEXCEPTIONS_H
+
+#include <atomic>
+#include <exception>
+#include <stdexcept>
+#include <string>
+
+
+class DeviceCommunicationException: public std::runtime_error
+{
+ std::string message;
+ static std::atomic_int occurred;
+public:
+ DeviceCommunicationException(std::string _msg): std::runtime_error(_msg), message(_msg){
+ ++occurred;
+ }
+ uint8_t getType() const {return 1;};
+// virtual const char* what() const throw() override {
+// return message.c_str();
+// }
+ static bool has_occurred(){ return occurred > 0; };
+ static void reset_occurred_flag(){ occurred = 0; };
+};
+
+class DeviceNotConnected: public DeviceCommunicationException {
+public:
+ DeviceNotConnected(std::string msg) : DeviceCommunicationException(msg){}
+ uint8_t getType() const {return 2;};
+};
+
+class DeviceSendingFailure: public DeviceCommunicationException {
+public:
+ DeviceSendingFailure(std::string msg) : DeviceCommunicationException(msg){}
+ uint8_t getType() const {return 3;};
+};
+
+class DeviceReceivingFailure: public DeviceCommunicationException {
+public:
+ DeviceReceivingFailure(std::string msg) : DeviceCommunicationException(msg){}
+ uint8_t getType() const {return 4;};
+};
+
+class InvalidCRCReceived: public DeviceReceivingFailure {
+public:
+ InvalidCRCReceived(std::string msg) : DeviceReceivingFailure(msg){}
+ uint8_t getType() const {return 5;};
+};
+
+
+#endif //LIBNITROKEY_DEVICECOMMUNICATIONEXCEPTIONS_H
diff --git a/include/LibraryException.h b/include/LibraryException.h
new file mode 100644
index 0000000..b9303ad
--- /dev/null
+++ b/include/LibraryException.h
@@ -0,0 +1,97 @@
+#ifndef LIBNITROKEY_LIBRARYEXCEPTION_H
+#define LIBNITROKEY_LIBRARYEXCEPTION_H
+
+#include <exception>
+#include <cstdint>
+#include <string>
+#include "log.h"
+
+class LibraryException: std::exception {
+public:
+ virtual uint8_t exception_id()= 0;
+};
+
+class TargetBufferSmallerThanSource: public LibraryException {
+public:
+ virtual uint8_t exception_id() override {
+ return 203;
+ }
+
+public:
+ size_t source_size;
+ size_t target_size;
+
+ TargetBufferSmallerThanSource(
+ size_t source_size, size_t target_size
+ ) : source_size(source_size), target_size(target_size) {}
+
+ virtual const char *what() const throw() override {
+ std::string s = " ";
+ auto ts = [](size_t x){ return std::to_string(x); };
+ std::string msg = std::string("Target buffer size is smaller than source: [source size, buffer size]")
+ +s+ ts(source_size) +s+ ts(target_size);
+ return msg.c_str();
+ }
+
+};
+
+class InvalidHexString : public LibraryException {
+public:
+ virtual uint8_t exception_id() override {
+ return 202;
+ }
+
+public:
+ uint8_t invalid_char;
+
+ InvalidHexString (uint8_t invalid_char) : invalid_char( invalid_char) {}
+
+ virtual const char *what() const throw() override {
+ return "Invalid character in hex string";
+ }
+
+};
+
+class InvalidSlotException : public LibraryException {
+public:
+ virtual uint8_t exception_id() override {
+ return 201;
+ }
+
+public:
+ uint8_t slot_selected;
+
+ InvalidSlotException(uint8_t slot_selected) : slot_selected(slot_selected) {}
+
+ virtual const char *what() const throw() override {
+ return "Wrong slot selected";
+ }
+
+};
+
+
+
+class TooLongStringException : public LibraryException {
+public:
+ virtual uint8_t exception_id() override {
+ return 200;
+ }
+
+ std::size_t size_source;
+ std::size_t size_destination;
+ std::string message;
+
+ TooLongStringException(size_t size_source, size_t size_destination, const std::string &message = "") : size_source(
+ size_source), size_destination(size_destination), message(message) {
+ LOG(std::string("TooLongStringException, size diff: ")+ std::to_string(size_source-size_destination), nitrokey::log::Loglevel::DEBUG);
+
+ }
+
+ virtual const char *what() const throw() override {
+ //TODO add sizes and message data to final message
+ return "Too long string has been supplied as an argument";
+ }
+
+};
+
+#endif //LIBNITROKEY_LIBRARYEXCEPTION_H
diff --git a/include/LongOperationInProgressException.h b/include/LongOperationInProgressException.h
new file mode 100644
index 0000000..5b441c0
--- /dev/null
+++ b/include/LongOperationInProgressException.h
@@ -0,0 +1,28 @@
+//
+// Created by sz on 24.10.16.
+//
+
+#ifndef LIBNITROKEY_LONGOPERATIONINPROGRESSEXCEPTION_H
+#define LIBNITROKEY_LONGOPERATIONINPROGRESSEXCEPTION_H
+
+#include "CommandFailedException.h"
+
+class LongOperationInProgressException : public CommandFailedException {
+
+public:
+ unsigned char progress_bar_value;
+
+ LongOperationInProgressException(
+ unsigned char _command_id, uint8_t last_command_status, unsigned char _progress_bar_value)
+ : CommandFailedException(_command_id, last_command_status), progress_bar_value(_progress_bar_value){
+ LOG(
+ std::string("LongOperationInProgressException, progress bar status: ")+
+ std::to_string(progress_bar_value), nitrokey::log::Loglevel::DEBUG);
+ }
+ virtual const char *what() const throw() {
+ return "Device returned busy status with long operation in progress";
+ }
+};
+
+
+#endif //LIBNITROKEY_LONGOPERATIONINPROGRESSEXCEPTION_H
diff --git a/include/NitrokeyManager.h b/include/NitrokeyManager.h
new file mode 100644
index 0000000..de14fbc
--- /dev/null
+++ b/include/NitrokeyManager.h
@@ -0,0 +1,189 @@
+#ifndef LIBNITROKEY_NITROKEYMANAGER_H
+#define LIBNITROKEY_NITROKEYMANAGER_H
+
+#include "device.h"
+#include "log.h"
+#include "device_proto.h"
+#include "stick10_commands.h"
+#include "stick10_commands_0.8.h"
+#include "stick20_commands.h"
+#include <vector>
+#include <memory>
+
+namespace nitrokey {
+ using namespace nitrokey::device;
+ using namespace std;
+ using namespace nitrokey::proto::stick10;
+ using namespace nitrokey::proto::stick20;
+ using namespace nitrokey::proto;
+ using namespace nitrokey::log;
+
+
+#ifdef __WIN32
+char * strndup(const char* str, size_t maxlen);
+#endif
+
+ class NitrokeyManager {
+ public:
+ static shared_ptr <NitrokeyManager> instance();
+
+ bool first_authenticate(const char *pin, const char *temporary_password);
+ bool write_HOTP_slot(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);
+ bool write_TOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window,
+ bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
+ const char *temporary_password);
+ string get_HOTP_code(uint8_t slot_number, const char *user_temporary_password);
+ string get_TOTP_code(uint8_t slot_number, uint64_t challenge, uint64_t last_totp_time,
+ uint8_t last_interval,
+ const char *user_temporary_password);
+ string get_TOTP_code(uint8_t slot_number, const char *user_temporary_password);
+ stick10::ReadSlot::ResponsePayload get_TOTP_slot_data(const uint8_t slot_number);
+ stick10::ReadSlot::ResponsePayload get_HOTP_slot_data(const uint8_t slot_number);
+
+ bool set_time(uint64_t time);
+ 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);
+ bool connect(const char *device_model);
+ bool connect();
+ bool disconnect();
+ bool is_connected() throw() ;
+ bool could_current_device_be_enumerated();
+ bool set_default_commands_delay(int delay);
+
+ DeviceModel get_connected_device_model() const;
+ void set_debug(bool state);
+ stick10::GetStatus::ResponsePayload get_status();
+ string get_status_as_string();
+ string get_serial_number();
+
+ const char * get_totp_slot_name(uint8_t slot_number);
+ const char * get_hotp_slot_name(uint8_t slot_number);
+
+ void change_user_PIN(const char *current_PIN, const char *new_PIN);
+ void change_admin_PIN(const char *current_PIN, const char *new_PIN);
+
+ void enable_password_safe(const char *user_pin);
+
+ vector <uint8_t> get_password_safe_slot_status();
+
+ uint8_t get_admin_retry_count();
+ uint8_t get_user_retry_count();
+
+ void lock_device();
+
+ const char *get_password_safe_slot_name(uint8_t slot_number);
+ const char *get_password_safe_slot_password(uint8_t slot_number);
+ const char *get_password_safe_slot_login(uint8_t slot_number);
+
+ void
+ write_password_safe_slot(uint8_t slot_number, const char *slot_name, const char *slot_login,
+ const char *slot_password);
+
+ void erase_password_safe_slot(uint8_t slot_number);
+
+ void user_authenticate(const char *user_password, const char *temporary_password);
+
+ void factory_reset(const char *admin_password);
+
+ void build_aes_key(const char *admin_password);
+
+ void unlock_user_password(const char *admin_password, const char *new_user_password);
+
+ void write_config(uint8_t numlock, uint8_t capslock, uint8_t scrolllock, bool enable_user_password,
+ bool delete_user_password, const char *admin_temporary_password);
+
+ vector<uint8_t> read_config();
+
+ bool is_AES_supported(const char *user_password);
+
+ void unlock_encrypted_volume(const char *user_password);
+ void lock_encrypted_volume();
+
+ void unlock_hidden_volume(const char *hidden_volume_password);
+ void lock_hidden_volume();
+
+ void set_unencrypted_read_only(const char *user_pin);
+
+ void set_unencrypted_read_write(const char *user_pin);
+
+ void export_firmware(const char *admin_pin);
+ void enable_firmware_update(const char *firmware_pin);
+
+ void clear_new_sd_card_warning(const char *admin_pin);
+
+ void fill_SD_card_with_random_data(const char *admin_pin);
+
+ uint8_t get_SD_card_size();
+
+ void change_update_password(const char *current_update_password, const char *new_update_password);
+
+ void create_hidden_volume(uint8_t slot_nr, uint8_t start_percent, uint8_t end_percent,
+ const char *hidden_volume_password);
+
+ void send_startup(uint64_t seconds_from_epoch);
+
+ const char * get_status_storage_as_string();
+ stick20::DeviceConfigurationResponsePacket::ResponsePayload get_status_storage();
+
+ const char *get_SD_usage_data_as_string();
+ std::pair<uint8_t,uint8_t> get_SD_usage_data();
+
+
+ int get_progress_bar_value();
+
+ ~NitrokeyManager();
+ bool is_authorization_command_supported();
+ bool is_320_OTP_secret_supported();
+
+
+ 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();
+
+ explicit NitrokeyManager();
+ void set_log_function(std::function<void(std::string)> log_function);
+ private:
+
+ static shared_ptr <NitrokeyManager> _instance;
+ std::shared_ptr<Device> device;
+
+ stick10::ReadSlot::ResponsePayload get_OTP_slot_data(const uint8_t slot_number);
+ bool is_valid_hotp_slot_number(uint8_t slot_number) const;
+ bool is_valid_totp_slot_number(uint8_t slot_number) const;
+ bool is_valid_password_safe_slot_number(uint8_t slot_number) const;
+ uint8_t get_internal_slot_number_for_hotp(uint8_t slot_number) const;
+ uint8_t get_internal_slot_number_for_totp(uint8_t slot_number) const;
+ bool erase_slot(uint8_t slot_number, const char *temporary_password);
+ const char * get_slot_name(uint8_t slot_number);
+
+ template <typename ProCommand, PasswordKind StoKind>
+ void change_PIN_general(const char *current_PIN, const char *new_PIN);
+
+ void 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);
+
+ void write_TOTP_slot_authorize(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window,
+ bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
+ const char *temporary_password);
+
+ void write_OTP_slot_no_authorize(uint8_t internal_slot_number, const char *slot_name, const char *secret,
+ uint64_t counter_or_interval,
+ bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
+ const char *temporary_password) const;
+ bool _disconnect_no_lock();
+
+ public:
+ bool set_current_device_speed(int retry_delay, int send_receive_delay);
+ void set_loglevel(Loglevel loglevel);
+
+ void set_loglevel(int loglevel);
+ };
+}
+
+
+
+#endif //LIBNITROKEY_NITROKEYMANAGER_H
diff --git a/include/command.h b/include/command.h
new file mode 100644
index 0000000..279754a
--- /dev/null
+++ b/include/command.h
@@ -0,0 +1,91 @@
+#ifndef COMMAND_H
+#define COMMAND_H
+#include <string>
+#include "command_id.h"
+#include "cxx_semantics.h"
+
+#define print_to_ss(x) ( ss << " " << (#x) <<":\t" << (x) << std::endl );
+#ifdef LOG_VOLATILE_DATA
+#define print_to_ss_volatile(x) print_to_ss(x);
+#else
+#define print_to_ss_volatile(x) ( ss << " " << (#x) <<":\t" << "***********" << std::endl );
+#endif
+#define hexdump_to_ss(x) (ss << #x":\n"\
+ << ::nitrokey::misc::hexdump((const uint8_t *) (&x), sizeof x, false));
+
+namespace nitrokey {
+ namespace proto {
+
+ template<CommandID cmd_id>
+ class Command : semantics::non_constructible {
+ public:
+ constexpr static CommandID command_id() { return cmd_id; }
+
+ template<typename T>
+ std::string dissect(const T &) {
+ return std::string("Payload dissection is unavailable");
+ }
+ };
+
+namespace stick20{
+ enum class PasswordKind : uint8_t {
+ User = 'P',
+ Admin = 'A',
+ AdminPrefixed
+ };
+
+ template<CommandID cmd_id, PasswordKind Tpassword_kind = PasswordKind::User, int password_length = 20>
+ class PasswordCommand : public Command<cmd_id> {
+ constexpr static CommandID _command_id() { return cmd_id; }
+ public:
+ struct CommandPayload {
+ uint8_t kind;
+ uint8_t password[password_length];
+
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss( kind );
+ print_to_ss_volatile(password);
+ return ss.str();
+ }
+ void set_kind_admin() {
+ kind = (uint8_t) 'A';
+ }
+ void set_kind_admin_prefixed() {
+ kind = (uint8_t) 'P';
+ }
+ void set_kind_user() {
+ kind = (uint8_t) 'P';
+ }
+
+ void set_defaults(){
+ set_kind(Tpassword_kind);
+ }
+
+ void set_kind(PasswordKind password_kind){
+ switch (password_kind){
+ case PasswordKind::Admin:
+ set_kind_admin();
+ break;
+ case PasswordKind::User:
+ set_kind_user();
+ break;
+ case PasswordKind::AdminPrefixed:
+ set_kind_admin_prefixed();
+ break;
+ }
+ };
+
+ } __packed;
+
+ //typedef Transaction<Command<cmd_id>::command_id(), struct CommandPayload, struct EmptyPayload>
+ // CommandTransaction;
+ using CommandTransaction = Transaction<cmd_id, CommandPayload, EmptyPayload>;
+ //using CommandTransaction = Transaction<_command_id(), CommandPayload, EmptyPayload>;
+
+ };
+ }
+ }
+}
+
+#endif
diff --git a/include/command_id.h b/include/command_id.h
new file mode 100644
index 0000000..d1246dd
--- /dev/null
+++ b/include/command_id.h
@@ -0,0 +1,124 @@
+#ifndef COMMAND_ID_H
+#define COMMAND_ID_H
+#include <stdint.h>
+
+namespace nitrokey {
+namespace proto {
+ namespace stick20 {
+ enum class device_status : uint8_t {
+ idle = 0,
+ ok,
+ busy,
+ wrong_password,
+ busy_progressbar,
+ password_matrix_ready,
+ no_user_password_unlock, // FIXME: translate on receive to command status error (fix in firmware?)
+ smartcard_error,
+ security_bit_active
+ };
+ const int CMD_START_VALUE = 0x20;
+ const int CMD_END_VALUE = 0x60;
+ }
+ namespace stick10 {
+ enum class command_status : uint8_t {
+ ok = 0,
+ wrong_CRC,
+ wrong_slot,
+ slot_not_programmed,
+ wrong_password = 4,
+ not_authorized,
+ timestamp_warning,
+ no_name_error,
+ not_supported,
+ unknown_command,
+ AES_dec_failed
+ };
+ enum class device_status : uint8_t {
+ ok = 0,
+ busy = 1,
+ error,
+ received_report,
+ };
+ }
+
+
+enum class CommandID : uint8_t {
+ GET_STATUS = 0x00,
+ WRITE_TO_SLOT = 0x01,
+ READ_SLOT_NAME = 0x02,
+ READ_SLOT = 0x03,
+ GET_CODE = 0x04,
+ WRITE_CONFIG = 0x05,
+ ERASE_SLOT = 0x06,
+ FIRST_AUTHENTICATE = 0x07,
+ AUTHORIZE = 0x08,
+ GET_PASSWORD_RETRY_COUNT = 0x09,
+ CLEAR_WARNING = 0x0A,
+ SET_TIME = 0x0B,
+ TEST_COUNTER = 0x0C,
+ TEST_TIME = 0x0D,
+ USER_AUTHENTICATE = 0x0E,
+ GET_USER_PASSWORD_RETRY_COUNT = 0x0F,
+ USER_AUTHORIZE = 0x10,
+ UNLOCK_USER_PASSWORD = 0x11,
+ LOCK_DEVICE = 0x12,
+ FACTORY_RESET = 0x13,
+ CHANGE_USER_PIN = 0x14,
+ CHANGE_ADMIN_PIN = 0x15,
+ WRITE_TO_SLOT_2 = 0x16,
+ SEND_OTP_DATA = 0x17,
+
+ ENABLE_CRYPTED_PARI = 0x20,
+ DISABLE_CRYPTED_PARI = 0x20 + 1,
+ ENABLE_HIDDEN_CRYPTED_PARI = 0x20 + 2,
+ DISABLE_HIDDEN_CRYPTED_PARI = 0x20 + 3,
+ ENABLE_FIRMWARE_UPDATE = 0x20 + 4, //enables update mode
+ EXPORT_FIRMWARE_TO_FILE = 0x20 + 5,
+ GENERATE_NEW_KEYS = 0x20 + 6,
+ FILL_SD_CARD_WITH_RANDOM_CHARS = 0x20 + 7,
+
+ WRITE_STATUS_DATA = 0x20 + 8, //@unused
+ ENABLE_READONLY_UNCRYPTED_LUN = 0x20 + 9,
+ ENABLE_READWRITE_UNCRYPTED_LUN = 0x20 + 10,
+
+ SEND_PASSWORD_MATRIX = 0x20 + 11, //@unused
+ SEND_PASSWORD_MATRIX_PINDATA = 0x20 + 12, //@unused
+ SEND_PASSWORD_MATRIX_SETUP = 0x20 + 13, //@unused
+
+ GET_DEVICE_STATUS = 0x20 + 14,
+ SEND_DEVICE_STATUS = 0x20 + 15,
+
+ SEND_HIDDEN_VOLUME_PASSWORD = 0x20 + 16, //@unused
+ SEND_HIDDEN_VOLUME_SETUP = 0x20 + 17,
+ SEND_PASSWORD = 0x20 + 18,
+ SEND_NEW_PASSWORD = 0x20 + 19,
+ CLEAR_NEW_SD_CARD_FOUND = 0x20 + 20,
+
+ SEND_STARTUP = 0x20 + 21,
+ SEND_CLEAR_STICK_KEYS_NOT_INITIATED = 0x20 + 22,
+ SEND_LOCK_STICK_HARDWARE = 0x20 + 23, //locks firmware upgrade
+
+ PRODUCTION_TEST = 0x20 + 24,
+ SEND_DEBUG_DATA = 0x20 + 25, //@unused
+
+ CHANGE_UPDATE_PIN = 0x20 + 26,
+
+ GET_PW_SAFE_SLOT_STATUS = 0x60,
+ GET_PW_SAFE_SLOT_NAME = 0x61,
+ GET_PW_SAFE_SLOT_PASSWORD = 0x62,
+ GET_PW_SAFE_SLOT_LOGINNAME = 0x63,
+ SET_PW_SAFE_SLOT_DATA_1 = 0x64,
+ SET_PW_SAFE_SLOT_DATA_2 = 0x65,
+ PW_SAFE_ERASE_SLOT = 0x66,
+ PW_SAFE_ENABLE = 0x67,
+ PW_SAFE_INIT_KEY = 0x68, //@unused
+ PW_SAFE_SEND_DATA = 0x69, //@unused
+ SD_CARD_HIGH_WATERMARK = 0x70,
+ DETECT_SC_AES = 0x6a,
+ NEW_AES_KEY = 0x6b
+};
+
+const char *commandid_to_string(CommandID id);
+}
+}
+#endif
diff --git a/include/cxx_semantics.h b/include/cxx_semantics.h
new file mode 100644
index 0000000..f358e8f
--- /dev/null
+++ b/include/cxx_semantics.h
@@ -0,0 +1,23 @@
+#ifndef CXX_SEMANTICS_H
+#define CXX_SEMANTICS_H
+
+#ifndef _MSC_VER
+#define __packed __attribute__((__packed__))
+#else
+#define __packed
+#endif
+
+#ifdef _MSC_VER
+#define strdup _strdup
+#endif
+
+/*
+ * There's no need to include Boost for a simple subset this project needs.
+ */
+namespace semantics {
+class non_constructible {
+ non_constructible() {}
+};
+}
+
+#endif
diff --git a/include/device.h b/include/device.h
new file mode 100644
index 0000000..8bc661a
--- /dev/null
+++ b/include/device.h
@@ -0,0 +1,142 @@
+#ifndef DEVICE_H
+#define DEVICE_H
+#include <chrono>
+#include "hidapi/hidapi.h"
+#include <cstdint>
+#include <string>
+
+#define HID_REPORT_SIZE 65
+
+#include <atomic>
+
+namespace nitrokey {
+namespace device {
+ using namespace std::chrono_literals;
+ using std::chrono::milliseconds;
+
+ struct EnumClassHash
+ {
+ template <typename T>
+ std::size_t operator()(T t) const
+ {
+ return static_cast<std::size_t>(t);
+ }
+ };
+
+enum class DeviceModel{
+ PRO,
+ STORAGE
+};
+
+#include <atomic>
+
+class Device {
+
+public:
+
+ struct ErrorCounters{
+ using cnt = std::atomic_int;
+ cnt wrong_CRC;
+ cnt CRC_other_than_awaited;
+ cnt busy;
+ cnt total_retries;
+ cnt sending_error;
+ cnt receiving_error;
+ cnt total_comm_runs;
+ cnt successful_storage_commands;
+ cnt command_successful_recv;
+ cnt recv_executed;
+ cnt sends_executed;
+ cnt busy_progressbar;
+ cnt command_result_not_equal_0_recv;
+ cnt communication_successful;
+ cnt low_level_reconnect;
+ std::string get_as_string();
+
+ } m_counters = {};
+
+
+ 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);
+
+ virtual ~Device();
+
+ // lack of device is not actually an error,
+ // so it doesn't throw
+ virtual bool connect();
+ virtual bool disconnect();
+
+ /*
+ * Sends packet of HID_REPORT_SIZE.
+ */
+ virtual int send(const void *packet);
+
+ /*
+ * Gets packet of HID_REPORT_SIZE.
+ * Can sleep. See below.
+ */
+ virtual int recv(void *packet);
+
+ /***
+ * Returns true if some device is visible by OS with given VID and PID
+ * whether the device is connected through HID API or not.
+ * @return true if visible by OS
+ */
+ bool could_be_enumerated();
+
+ void show_stats();
+// ErrorCounters get_stats(){ return m_counters; }
+ int get_retry_receiving_count() const { return m_retry_receiving_count; };
+ int get_retry_sending_count() const { return m_retry_sending_count; };
+ std::chrono::milliseconds get_retry_timeout() const { return m_retry_timeout; };
+ std::chrono::milliseconds get_send_receive_delay() const {return m_send_receive_delay;}
+
+ int get_last_command_status() {int a = std::atomic_exchange(&last_command_status, static_cast<uint8_t>(0)); return a;};
+ void set_last_command_status(uint8_t _err) { last_command_status = _err;} ;
+ bool last_command_sucessfull() const {return last_command_status == 0;};
+ DeviceModel get_device_model() const {return m_model;}
+ void set_receiving_delay(std::chrono::milliseconds delay);
+ void set_retry_delay(std::chrono::milliseconds delay);
+ static void set_default_device_speed(int delay);
+ void setDefaultDelay();
+
+private:
+ std::atomic<uint8_t> last_command_status;
+ void _reconnect();
+ bool _connect();
+ bool _disconnect();
+
+protected:
+ const uint16_t m_vid;
+ const uint16_t m_pid;
+ const DeviceModel m_model;
+
+ /*
+ * While the project uses Signal11 portable HIDAPI
+ * library, there's no way of doing it asynchronously,
+ * hence polling.
+ */
+ const int m_retry_sending_count;
+ const int m_retry_receiving_count;
+ std::chrono::milliseconds m_retry_timeout;
+ std::chrono::milliseconds m_send_receive_delay;
+ std::atomic<hid_device *>mp_devhandle;
+
+ static std::atomic_int instances_count;
+ static std::chrono::milliseconds default_delay ;
+};
+
+class Stick10 : public Device {
+ public:
+ Stick10();
+
+};
+
+class Stick20 : public Device {
+ public:
+ Stick20();
+};
+}
+}
+#endif
diff --git a/include/device_proto.h b/include/device_proto.h
new file mode 100644
index 0000000..388c721
--- /dev/null
+++ b/include/device_proto.h
@@ -0,0 +1,467 @@
+#ifndef DEVICE_PROTO_H
+#define DEVICE_PROTO_H
+
+#include <utility>
+#include <thread>
+#include <type_traits>
+#include <stdexcept>
+#include <string>
+// a local version for compatibility with Windows
+#include <stdint.h>
+#include "cxx_semantics.h"
+#include "device.h"
+#include "misc.h"
+#include "log.h"
+#include "command_id.h"
+#include "dissect.h"
+#include "CommandFailedException.h"
+#include "LongOperationInProgressException.h"
+
+#define STICK20_UPDATE_MODE_VID 0x03EB
+#define STICK20_UPDATE_MODE_PID 0x2FF1
+
+#define PAYLOAD_SIZE 53
+#define PWS_SLOT_COUNT 16
+#define PWS_SLOTNAME_LENGTH 11
+#define PWS_PASSWORD_LENGTH 20
+#define PWS_LOGINNAME_LENGTH 32
+
+#define PWS_SEND_PASSWORD 0
+#define PWS_SEND_LOGINNAME 1
+#define PWS_SEND_TAB 2
+#define PWS_SEND_CR 3
+
+#include <mutex>
+#include "DeviceCommunicationExceptions.h"
+#define bzero(b,len) (memset((b), '\0', (len)), (void) 0)
+
+namespace nitrokey {
+ namespace proto {
+ extern std::mutex send_receive_mtx;
+
+
+/*
+ * POD types for HID proto commands
+ * Instances are meant to be __packed.
+ *
+ * TODO (future) support for Big Endian
+ */
+#pragma pack (push,1)
+/*
+ * Every packet is a USB HID report (check USB spec)
+ */
+ template<CommandID cmd_id, typename Payload>
+ struct HIDReport {
+ uint8_t _zero;
+ CommandID command_id; // uint8_t
+ union {
+ uint8_t _padding[HID_REPORT_SIZE - 6];
+ Payload payload;
+ } __packed;
+ uint32_t crc;
+
+ // POD types can't have non-default constructors
+ // used in Transaction<>::run()
+ void initialize() {
+ bzero(this, sizeof *this);
+ command_id = cmd_id;
+ }
+
+ uint32_t calculate_CRC() const {
+ // w/o leading zero, a part of each HID packet
+ // w/o 4-byte crc
+ return misc::stm_crc32((const uint8_t *) (this) + 1,
+ (size_t) (HID_REPORT_SIZE - 5));
+ }
+
+ void update_CRC() { crc = calculate_CRC(); }
+
+ bool isCRCcorrect() const { return crc == calculate_CRC(); }
+
+ bool isValid() const {
+ return true;
+ // return !_zero && payload.isValid() && isCRCcorrect();
+ }
+
+ operator std::string() const {
+ // Packet type is known upfront in normal operation.
+ // Can't be used to dissect random packets.
+ return QueryDissector<cmd_id, decltype(*this)>::dissect(*this);
+ }
+ } __packed;
+
+/*
+ * Response payload (the parametrized type inside struct HIDReport)
+ *
+ * command_id member in incoming HIDReport structure carries the command
+ * type last used.
+ */
+ namespace DeviceResponseConstants{
+ //magic numbers from firmware
+ static constexpr auto storage_status_absolute_address = 21;
+ static constexpr auto storage_data_absolute_address = storage_status_absolute_address + 5;
+ static constexpr auto header_size = 8; //from _zero to last_command_status inclusive
+ static constexpr auto footer_size = 4; //crc
+ static constexpr auto wrapping_size = header_size + footer_size;
+ }
+
+ template<CommandID cmd_id, typename ResponsePayload>
+ struct DeviceResponse {
+ static constexpr auto storage_status_padding_size =
+ DeviceResponseConstants::storage_status_absolute_address - DeviceResponseConstants::header_size;
+
+ uint8_t _zero;
+ uint8_t device_status;
+ uint8_t command_id; // originally last_command_type
+ uint32_t last_command_crc;
+ uint8_t last_command_status;
+
+ union {
+ uint8_t _padding[HID_REPORT_SIZE - DeviceResponseConstants::wrapping_size];
+ ResponsePayload payload;
+ struct {
+ uint8_t _storage_status_padding[storage_status_padding_size];
+ uint8_t command_counter;
+ uint8_t command_id;
+ uint8_t device_status; //@see stick20::device_status
+ uint8_t progress_bar_value;
+ } __packed storage_status;
+ } __packed;
+
+ uint32_t crc;
+
+ void initialize() { bzero(this, sizeof *this); }
+
+ uint32_t calculate_CRC() const {
+ // w/o leading zero, a part of each HID packet
+ // w/o 4-byte crc
+ return misc::stm_crc32((const uint8_t *) (this) + 1,
+ (size_t) (HID_REPORT_SIZE - 5));
+ }
+
+ void update_CRC() { crc = calculate_CRC(); }
+ bool isCRCcorrect() const { return crc == calculate_CRC(); }
+ bool isValid() const {
+ // return !_zero && payload.isValid() && isCRCcorrect() &&
+ // command_id == (uint8_t)(cmd_id);
+ return crc != 0;
+ }
+
+ operator std::string() const {
+ return ResponseDissector<cmd_id, decltype(*this)>::dissect(*this);
+ }
+ } __packed;
+
+ struct EmptyPayload {
+ bool isValid() const { return true; }
+
+ std::string dissect() const { return std::string("Empty Payload."); }
+ } __packed;
+
+ template<typename command_packet, typename response_payload>
+ class ClearingProxy {
+ public:
+ ClearingProxy(command_packet &p) {
+ packet = p;
+ bzero(&p, sizeof(p));
+ }
+
+ ~ClearingProxy() {
+ bzero(&packet, sizeof(packet));
+ }
+
+ response_payload &data() {
+ return packet.payload;
+ }
+
+ command_packet packet;
+ };
+
+ template<CommandID cmd_id, typename command_payload, typename response_payload>
+ class Transaction : semantics::non_constructible {
+ public:
+ // Types declared in command class scope can't be reached from there.
+ typedef command_payload CommandPayload;
+ typedef response_payload ResponsePayload;
+
+
+ typedef struct HIDReport<cmd_id, CommandPayload> OutgoingPacket;
+ typedef struct DeviceResponse<cmd_id, ResponsePayload> ResponsePacket;
+#pragma pack (pop)
+
+ static_assert(std::is_pod<OutgoingPacket>::value,
+ "outgoingpacket must be a pod type");
+ static_assert(std::is_pod<ResponsePacket>::value,
+ "ResponsePacket must be a POD type");
+ static_assert(sizeof(OutgoingPacket) == HID_REPORT_SIZE,
+ "OutgoingPacket type is not the right size");
+ static_assert(sizeof(ResponsePacket) == HID_REPORT_SIZE,
+ "ResponsePacket type is not the right size");
+
+ static uint32_t getCRC(
+ const command_payload &payload) {
+ OutgoingPacket outp;
+ outp.initialize();
+ outp.payload = payload;
+ outp.update_CRC();
+ return outp.crc;
+ }
+
+ template<typename T>
+ static void clear_packet(T &st) {
+ bzero(&st, sizeof(st));
+ }
+
+ static ClearingProxy<ResponsePacket, response_payload> run(std::shared_ptr<device::Device> dev,
+ const command_payload &payload) {
+ using namespace ::nitrokey::device;
+ using namespace ::nitrokey::log;
+ using namespace std::chrono_literals;
+
+ std::lock_guard<std::mutex> guard(send_receive_mtx);
+
+ LOG(__FUNCTION__, Loglevel::DEBUG_L2);
+
+ if (dev == nullptr){
+ LOG(std::string("Throw: Device not initialized"), Loglevel::DEBUG_L1);
+ throw DeviceNotConnected("Device not initialized");
+ }
+ dev->m_counters.total_comm_runs++;
+
+ int status;
+ OutgoingPacket outp;
+ ResponsePacket resp;
+
+ // POD types can't have non-default constructors
+ outp.initialize();
+ resp.initialize();
+
+ outp.payload = payload;
+ outp.update_CRC();
+
+ LOG("-------------------", Loglevel::DEBUG);
+ LOG("Outgoing HID packet:", Loglevel::DEBUG);
+ LOG(static_cast<std::string>(outp), Loglevel::DEBUG);
+ LOG(std::string("=> ") + std::string(commandid_to_string(static_cast<CommandID>(outp.command_id))), Loglevel::DEBUG_L1);
+
+
+ if (!outp.isValid()) {
+ LOG(std::string("Throw: Invalid outgoing packet"), Loglevel::DEBUG_L1);
+ throw DeviceSendingFailure("Invalid outgoing packet");
+ }
+
+ bool successful_communication = false;
+ int receiving_retry_counter = 0;
+ int sending_retry_counter = dev->get_retry_sending_count();
+ while (sending_retry_counter-- > 0) {
+ dev->m_counters.sends_executed++;
+ status = dev->send(&outp);
+ if (status <= 0){
+ //FIXME early disconnection not yet working properly
+// LOG("Encountered communication error, disconnecting device", Loglevel::DEBUG_L2);
+// dev->disconnect();
+ dev->m_counters.sending_error++;
+ LOG(std::string("Throw: Device error while sending command "), Loglevel::DEBUG_L1);
+ throw DeviceSendingFailure(
+ std::string("Device error while sending command ") +
+ std::to_string(status));
+ }
+
+ std::this_thread::sleep_for(dev->get_send_receive_delay());
+
+ // FIXME make checks done in device:recv here
+ receiving_retry_counter = dev->get_retry_receiving_count();
+ int busy_counter = 0;
+ auto retry_timeout = dev->get_retry_timeout();
+ while (receiving_retry_counter-- > 0) {
+ dev->m_counters.recv_executed++;
+ status = dev->recv(&resp);
+
+ if (dev->get_device_model() == DeviceModel::STORAGE &&
+ resp.command_id >= stick20::CMD_START_VALUE &&
+ resp.command_id < stick20::CMD_END_VALUE ) {
+ LOG(std::string("Detected storage device cmd, status: ") +
+ std::to_string(resp.storage_status.device_status), Loglevel::DEBUG_L2);
+
+ resp.last_command_status = static_cast<uint8_t>(stick10::command_status::ok);
+ switch (static_cast<stick20::device_status>(resp.storage_status.device_status)) {
+ case stick20::device_status::idle :
+ case stick20::device_status::ok:
+ resp.device_status = static_cast<uint8_t>(stick10::device_status::ok);
+ break;
+ case stick20::device_status::busy:
+ case stick20::device_status::busy_progressbar: //TODO this will be modified later for getting progressbar status
+ resp.device_status = static_cast<uint8_t>(stick10::device_status::busy);
+ break;
+ case stick20::device_status::wrong_password:
+ resp.last_command_status = static_cast<uint8_t>(stick10::command_status::wrong_password);
+ resp.device_status = static_cast<uint8_t>(stick10::device_status::ok);
+ break;
+ case stick20::device_status::no_user_password_unlock:
+ resp.last_command_status = static_cast<uint8_t>(stick10::command_status::AES_dec_failed);
+ resp.device_status = static_cast<uint8_t>(stick10::device_status::ok);
+ default:
+ LOG(std::string("Unknown storage device status, cannot translate: ") +
+ std::to_string(resp.storage_status.device_status), Loglevel::DEBUG);
+ resp.device_status = resp.storage_status.device_status;
+ break;
+ };
+ }
+
+ //Some of the commands return wrong CRC, for now skip checking it (TODO list and report)
+ //if (resp.device_status == 0 && resp.last_command_crc == outp.crc && resp.isCRCcorrect()) break;
+ auto CRC_equal_awaited = true; // resp.last_command_crc == outp.crc;
+ if (resp.device_status == static_cast<uint8_t>(stick10::device_status::ok) &&
+ CRC_equal_awaited && resp.isValid()){
+ successful_communication = true;
+ break;
+ }
+ if (resp.device_status == static_cast<uint8_t>(stick10::device_status::busy)) {
+ dev->m_counters.busy++;
+
+ if (busy_counter++<10) {
+ receiving_retry_counter++;
+ LOG("Status busy, not decreasing receiving_retry_counter counter: " +
+ std::to_string(receiving_retry_counter), Loglevel::DEBUG_L2);
+ } else {
+ retry_timeout *= 2;
+ retry_timeout = std::min(retry_timeout, 300ms);
+ busy_counter = 0;
+ LOG("Status busy, decreasing receiving_retry_counter counter: " +
+ std::to_string(receiving_retry_counter) + ", current delay:"
+ + std::to_string(retry_timeout.count()), Loglevel::DEBUG);
+ LOG(std::string("Busy retry ")
+ + std::to_string(resp.storage_status.device_status)
+ + " "
+ + std::to_string(retry_timeout.count())
+ + " "
+ + std::to_string(receiving_retry_counter)
+ , Loglevel::DEBUG_L1);
+ }
+ }
+ if (resp.device_status == static_cast<uint8_t>(stick10::device_status::busy) &&
+ static_cast<stick20::device_status>(resp.storage_status.device_status)
+ == stick20::device_status::busy_progressbar){
+ successful_communication = true;
+ break;
+ }
+ LOG(std::string("Retry status - dev status, awaited cmd crc, correct packet CRC: ")
+ + std::to_string(resp.device_status) +
+ " " + std::to_string(CRC_equal_awaited) +
+ " " + std::to_string(resp.isCRCcorrect()), Loglevel::DEBUG_L2);
+
+ if (!resp.isCRCcorrect()) dev->m_counters.wrong_CRC++;
+ if (!CRC_equal_awaited) dev->m_counters.CRC_other_than_awaited++;
+
+
+ LOG(
+ "Device is not ready or received packet's last CRC is not equal to sent CRC packet, retrying...",
+ Loglevel::DEBUG_L2);
+ LOG("Invalid incoming HID packet:", Loglevel::DEBUG_L2);
+ LOG(static_cast<std::string>(resp), Loglevel::DEBUG_L2);
+ dev->m_counters.total_retries++;
+ LOG(".", Loglevel::DEBUG_L1);
+ std::this_thread::sleep_for(retry_timeout);
+ continue;
+ }
+ if (successful_communication) break;
+ LOG(std::string("Resending (outer loop) "), Loglevel::DEBUG_L2);
+ LOG(std::string("sending_retry_counter count: ") + std::to_string(sending_retry_counter),
+ Loglevel::DEBUG);
+ }
+
+ if(resp.last_command_crc != outp.crc){
+ LOG(std::string("Accepting response with CRC other than expected ")
+ + "Command ID: " + std::to_string(resp.command_id) + " " +
+ commandid_to_string(static_cast<CommandID>(resp.command_id)) + " "
+ + "Reported by response and expected: " + std::to_string(resp.last_command_crc) + "!=" + std::to_string(outp.crc),
+ Loglevel::WARNING
+ );
+ }
+
+ dev->set_last_command_status(resp.last_command_status); // FIXME should be handled on device.recv
+
+ clear_packet(outp);
+
+
+ if (status <= 0) {
+ dev->m_counters.receiving_error++;
+ LOG(std::string("Throw: Device error while executing command "), Loglevel::DEBUG_L1);
+ throw DeviceReceivingFailure( //FIXME replace with CriticalErrorException
+ std::string("Device error while executing command ") +
+ std::to_string(status));
+ }
+
+ LOG(std::string("<= ") +
+ std::string(
+ commandid_to_string(static_cast<CommandID>(resp.command_id))
+ + std::string(" ")
+ + std::to_string(resp.device_status)
+ + std::string(" ")
+ + std::to_string(resp.storage_status.device_status)
+// + std::to_string( status_translate_command(resp.storage_status.device_status))
+ ), Loglevel::DEBUG_L1);
+
+ LOG("Incoming HID packet:", Loglevel::DEBUG);
+ LOG(static_cast<std::string>(resp), Loglevel::DEBUG);
+ if (dev->get_retry_receiving_count() - receiving_retry_counter > 2) {
+ LOG(std::string("Packet received with receiving_retry_counter count: ") +
+ std::to_string(receiving_retry_counter),
+ Loglevel::DEBUG_L1);
+ }
+
+ if (resp.device_status == static_cast<uint8_t>(stick10::device_status::busy) &&
+ static_cast<stick20::device_status>(resp.storage_status.device_status)
+ == stick20::device_status::busy_progressbar){
+ dev->m_counters.busy_progressbar++;
+ LOG(std::string("Throw: Long operation in progress exception"), Loglevel::DEBUG_L1);
+ throw LongOperationInProgressException(
+ resp.command_id, resp.device_status, resp.storage_status.progress_bar_value);
+ }
+
+ if (!resp.isValid()) {
+ LOG(std::string("Throw: Invalid incoming packet"), Loglevel::DEBUG_L1);
+ throw InvalidCRCReceived("Invalid incoming packet");
+ }
+ if (receiving_retry_counter <= 0){
+ LOG(std::string("Throw: \"Maximum receiving_retry_counter count reached for receiving response from the device!\""
+ + std::to_string(receiving_retry_counter)), Loglevel::DEBUG_L1);
+ throw DeviceReceivingFailure(
+ "Maximum receiving_retry_counter count reached for receiving response from the device!");
+ }
+ dev->m_counters.communication_successful++;
+
+ if (resp.last_command_status != static_cast<uint8_t>(stick10::command_status::ok)){
+ dev->m_counters.command_result_not_equal_0_recv++;
+ LOG(std::string("Throw: CommandFailedException"), Loglevel::DEBUG_L1);
+ throw CommandFailedException(resp.command_id, resp.last_command_status);
+ }
+
+ dev->m_counters.command_successful_recv++;
+
+ if (dev->get_device_model() == DeviceModel::STORAGE &&
+ resp.command_id >= stick20::CMD_START_VALUE &&
+ resp.command_id < stick20::CMD_END_VALUE ) {
+ dev->m_counters.successful_storage_commands++;
+ }
+
+ if (!resp.isCRCcorrect())
+ LOG(std::string("Accepting response from device with invalid CRC. ")
+ + "Command ID: " + std::to_string(resp.command_id) + " " +
+ commandid_to_string(static_cast<CommandID>(resp.command_id)) + " "
+ + "Reported and calculated: " + std::to_string(resp.crc) + "!=" + std::to_string(resp.calculate_CRC()),
+ Loglevel::WARNING
+ );
+
+ // See: DeviceResponse
+ return resp;
+ }
+
+ static ClearingProxy<ResponsePacket, response_payload> run(std::shared_ptr<device::Device> dev) {
+ command_payload empty_payload;
+ return run(dev, empty_payload);
+ }
+ };
+ }
+}
+#endif
diff --git a/include/dissect.h b/include/dissect.h
new file mode 100644
index 0000000..06b99fa
--- /dev/null
+++ b/include/dissect.h
@@ -0,0 +1,124 @@
+/*
+ * Protocol packet dissection
+ */
+#ifndef DISSECT_H
+#define DISSECT_H
+#include <string>
+#include <sstream>
+#include <iomanip>
+#include "misc.h"
+#include "cxx_semantics.h"
+#include "command_id.h"
+#include "device_proto.h"
+
+namespace nitrokey {
+namespace proto {
+
+template <CommandID id, class HIDPacket>
+class QueryDissector : semantics::non_constructible {
+ public:
+ static std::string dissect(const HIDPacket &pod) {
+ std::stringstream out;
+
+#ifdef LOG_VOLATILE_DATA
+ out << "Raw HID packet:" << std::endl;
+ out << ::nitrokey::misc::hexdump((const uint8_t *)(&pod), sizeof pod);
+#endif
+
+ out << "Contents:" << std::endl;
+ out << "Command ID:\t" << commandid_to_string((CommandID)(pod.command_id))
+ << std::endl;
+ out << "CRC:\t"
+ << std::hex << std::setw(2) << std::setfill('0')
+ << pod.crc << std::endl;
+
+ out << "Payload:" << std::endl;
+ out << pod.payload.dissect();
+ return out.str();
+ }
+};
+
+
+
+
+template <CommandID id, class HIDPacket>
+class ResponseDissector : semantics::non_constructible {
+ public:
+ static std::string status_translate_device(int status){
+ auto enum_status = static_cast<proto::stick10::device_status>(status);
+ switch (enum_status){
+ case stick10::device_status::ok: return "OK";
+ case stick10::device_status::busy: return "BUSY";
+ case stick10::device_status::error: return "ERROR";
+ case stick10::device_status::received_report: return "RECEIVED_REPORT";
+ }
+ return std::string("UNKNOWN: ") + std::to_string(status);
+ }
+
+ static std::string to_upper(std::string str){
+ for (auto & c: str) c = toupper(c);
+ return str;
+ }
+ static std::string status_translate_command(int status){
+ auto enum_status = static_cast<proto::stick10::command_status >(status);
+ switch (enum_status) {
+#define p(X) case X: return to_upper(std::string(#X));
+ p(stick10::command_status::ok)
+ p(stick10::command_status::wrong_CRC)
+ p(stick10::command_status::wrong_slot)
+ p(stick10::command_status::slot_not_programmed)
+ p(stick10::command_status::wrong_password)
+ p(stick10::command_status::not_authorized)
+ p(stick10::command_status::timestamp_warning)
+ p(stick10::command_status::no_name_error)
+ p(stick10::command_status::not_supported)
+ p(stick10::command_status::unknown_command)
+ p(stick10::command_status::AES_dec_failed)
+#undef p
+ }
+ return std::string("UNKNOWN: ") + std::to_string(status);
+ }
+
+ static std::string dissect(const HIDPacket &pod) {
+ std::stringstream out;
+
+ // FIXME use values from firmware (possibly generate separate
+ // header automatically)
+
+#ifdef LOG_VOLATILE_DATA
+ out << "Raw HID packet:" << std::endl;
+ out << ::nitrokey::misc::hexdump((const uint8_t *)(&pod), sizeof pod);
+#endif
+
+ out << "Device status:\t" << pod.device_status + 0 << " "
+ << status_translate_device(pod.device_status) << std::endl;
+ out << "Command ID:\t" << commandid_to_string((CommandID)(pod.command_id)) << " hex: " << std::hex << (int)pod.command_id
+ << std::endl;
+ out << "Last command CRC:\t"
+ << std::hex << std::setw(2) << std::setfill('0')
+ << pod.last_command_crc << std::endl;
+ out << "Last command status:\t" << pod.last_command_status + 0 << " "
+ << status_translate_command(pod.last_command_status) << std::endl;
+ out << "CRC:\t"
+ << std::hex << std::setw(2) << std::setfill('0')
+ << pod.crc << std::endl;
+ if((int)pod.command_id == pod.storage_status.command_id){
+ out << "Storage stick status (where applicable):" << std::endl;
+#define d(x) out << " "#x": \t"<< std::hex << std::setw(2) \
+ << std::setfill('0')<< static_cast<int>(x) << std::endl;
+ d(pod.storage_status.command_counter);
+ d(pod.storage_status.command_id);
+ d(pod.storage_status.device_status);
+ d(pod.storage_status.progress_bar_value);
+#undef d
+ }
+
+ out << "Payload:" << std::endl;
+ out << pod.payload.dissect();
+ return out.str();
+ }
+};
+}
+}
+
+#endif
diff --git a/include/hidapi/hidapi.h b/include/hidapi/hidapi.h
new file mode 100644
index 0000000..e5bc2dc
--- /dev/null
+++ b/include/hidapi/hidapi.h
@@ -0,0 +1,391 @@
+/*******************************************************
+ HIDAPI - Multi-Platform library for
+ communication with HID devices.
+
+ Alan Ott
+ Signal 11 Software
+
+ 8/22/2009
+
+ Copyright 2009, All Rights Reserved.
+
+ At the discretion of the user of this library,
+ this software may be licensed under the terms of the
+ GNU General Public License v3, a BSD-Style license, or the
+ original HIDAPI license as outlined in the LICENSE.txt,
+ LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
+ files located at the root of the source distribution.
+ These files may also be found in the public source
+ code repository located at:
+ http://github.com/signal11/hidapi .
+********************************************************/
+
+/** @file
+ * @defgroup API hidapi API
+ */
+
+#ifndef HIDAPI_H__
+#define HIDAPI_H__
+
+#include <wchar.h>
+
+#ifdef _WIN32
+ #define HID_API_EXPORT __declspec(dllexport)
+ #define HID_API_CALL
+#else
+ #define HID_API_EXPORT /**< API export macro */
+ #define HID_API_CALL /**< API call macro */
+#endif
+
+#define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ struct hid_device_;
+ typedef struct hid_device_ hid_device; /**< opaque hidapi structure */
+
+ /** hidapi info structure */
+ struct hid_device_info {
+ /** Platform-specific device path */
+ char *path;
+ /** Device Vendor ID */
+ unsigned short vendor_id;
+ /** Device Product ID */
+ unsigned short product_id;
+ /** Serial Number */
+ wchar_t *serial_number;
+ /** Device Release Number in binary-coded decimal,
+ also known as Device Version Number */
+ unsigned short release_number;
+ /** Manufacturer String */
+ wchar_t *manufacturer_string;
+ /** Product string */
+ wchar_t *product_string;
+ /** Usage Page for this Device/Interface
+ (Windows/Mac only). */
+ unsigned short usage_page;
+ /** Usage for this Device/Interface
+ (Windows/Mac only).*/
+ unsigned short usage;
+ /** The USB interface which this logical device
+ represents. Valid on both Linux implementations
+ in all cases, and valid on the Windows implementation
+ only if the device contains more than one interface. */
+ int interface_number;
+
+ /** Pointer to the next device */
+ struct hid_device_info *next;
+ };
+
+
+ /** @brief Initialize the HIDAPI library.
+
+ This function initializes the HIDAPI library. Calling it is not
+ strictly necessary, as it will be called automatically by
+ hid_enumerate() and any of the hid_open_*() functions if it is
+ needed. This function should be called at the beginning of
+ execution however, if there is a chance of HIDAPI handles
+ being opened by different threads simultaneously.
+
+ @ingroup API
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_init(void);
+
+ /** @brief Finalize the HIDAPI library.
+
+ This function frees all of the static data associated with
+ HIDAPI. It should be called at the end of execution to avoid
+ memory leaks.
+
+ @ingroup API
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_exit(void);
+
+ /** @brief Enumerate the HID Devices.
+
+ This function returns a linked list of all the HID devices
+ attached to the system which match vendor_id and product_id.
+ If @p vendor_id is set to 0 then any vendor matches.
+ If @p product_id is set to 0 then any product matches.
+ If @p vendor_id and @p product_id are both set to 0, then
+ all HID devices will be returned.
+
+ @ingroup API
+ @param vendor_id The Vendor ID (VID) of the types of device
+ to open.
+ @param product_id The Product ID (PID) of the types of
+ device to open.
+
+ @returns
+ This function returns a pointer to a linked list of type
+ struct #hid_device, containing information about the HID devices
+ attached to the system, or NULL in the case of failure. Free
+ this linked list by calling hid_free_enumeration().
+ */
+ struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id);
+
+ /** @brief Free an enumeration Linked List
+
+ This function frees a linked list created by hid_enumerate().
+
+ @ingroup API
+ @param devs Pointer to a list of struct_device returned from
+ hid_enumerate().
+ */
+ void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs);
+
+ /** @brief Open a HID device using a Vendor ID (VID), Product ID
+ (PID) and optionally a serial number.
+
+ If @p serial_number is NULL, the first device with the
+ specified VID and PID is opened.
+
+ @ingroup API
+ @param vendor_id The Vendor ID (VID) of the device to open.
+ @param product_id The Product ID (PID) of the device to open.
+ @param serial_number The Serial Number of the device to open
+ (Optionally NULL).
+
+ @returns
+ This function returns a pointer to a #hid_device object on
+ success or NULL on failure.
+ */
+ HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number);
+
+ /** @brief Open a HID device by its path name.
+
+ The path name be determined by calling hid_enumerate(), or a
+ platform-specific path name can be used (eg: /dev/hidraw0 on
+ Linux).
+
+ @ingroup API
+ @param path The path name of the device to open
+
+ @returns
+ This function returns a pointer to a #hid_device object on
+ success or NULL on failure.
+ */
+ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path);
+
+ /** @brief Write an Output report to a HID device.
+
+ The first byte of @p data[] must contain the Report ID. For
+ devices which only support a single report, this must be set
+ to 0x0. The remaining bytes contain the report data. Since
+ the Report ID is mandatory, calls to hid_write() will always
+ contain one more byte than the report contains. For example,
+ if a hid report is 16 bytes long, 17 bytes must be passed to
+ hid_write(), the Report ID (or 0x0, for devices with a
+ single report), followed by the report data (16 bytes). In
+ this example, the length passed in would be 17.
+
+ hid_write() will send the data on the first OUT endpoint, if
+ one exists. If it does not, it will send the data through
+ the Control Endpoint (Endpoint 0).
+
+ @ingroup API
+ @param device A device handle returned from hid_open().
+ @param data The data to send, including the report number as
+ the first byte.
+ @param length The length in bytes of the data to send.
+
+ @returns
+ This function returns the actual number of bytes written and
+ -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length);
+
+ /** @brief Read an Input report from a HID device with timeout.
+
+ Input reports are returned
+ to the host through the INTERRUPT IN endpoint. The first byte will
+ contain the Report number if the device uses numbered reports.
+
+ @ingroup API
+ @param device A device handle returned from hid_open().
+ @param data A buffer to put the read data into.
+ @param length The number of bytes to read. For devices with
+ multiple reports, make sure to read an extra byte for
+ the report number.
+ @param milliseconds timeout in milliseconds or -1 for blocking wait.
+
+ @returns
+ This function returns the actual number of bytes read and
+ -1 on error. If no packet was available to be read within
+ the timeout period, this function returns 0.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds);
+
+ /** @brief Read an Input report from a HID device.
+
+ Input reports are returned
+ to the host through the INTERRUPT IN endpoint. The first byte will
+ contain the Report number if the device uses numbered reports.
+
+ @ingroup API
+ @param device A device handle returned from hid_open().
+ @param data A buffer to put the read data into.
+ @param length The number of bytes to read. For devices with
+ multiple reports, make sure to read an extra byte for
+ the report number.
+
+ @returns
+ This function returns the actual number of bytes read and
+ -1 on error. If no packet was available to be read and
+ the handle is in non-blocking mode, this function returns 0.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length);
+
+ /** @brief Set the device handle to be non-blocking.
+
+ In non-blocking mode calls to hid_read() will return
+ immediately with a value of 0 if there is no data to be
+ read. In blocking mode, hid_read() will wait (block) until
+ there is data to read before returning.
+
+ Nonblocking can be turned on and off at any time.
+
+ @ingroup API
+ @param device A device handle returned from hid_open().
+ @param nonblock enable or not the nonblocking reads
+ - 1 to enable nonblocking
+ - 0 to disable nonblocking.
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *device, int nonblock);
+
+ /** @brief Send a Feature report to the device.
+
+ Feature reports are sent over the Control endpoint as a
+ Set_Report transfer. The first byte of @p data[] must
+ contain the Report ID. For devices which only support a
+ single report, this must be set to 0x0. The remaining bytes
+ contain the report data. Since the Report ID is mandatory,
+ calls to hid_send_feature_report() will always contain one
+ more byte than the report contains. For example, if a hid
+ report is 16 bytes long, 17 bytes must be passed to
+ hid_send_feature_report(): the Report ID (or 0x0, for
+ devices which do not use numbered reports), followed by the
+ report data (16 bytes). In this example, the length passed
+ in would be 17.
+
+ @ingroup API
+ @param device A device handle returned from hid_open().
+ @param data The data to send, including the report number as
+ the first byte.
+ @param length The length in bytes of the data to send, including
+ the report number.
+
+ @returns
+ This function returns the actual number of bytes written and
+ -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length);
+
+ /** @brief Get a feature report from a HID device.
+
+ Set the first byte of @p data[] to the Report ID of the
+ report to be read. Make sure to allow space for this
+ extra byte in @p data[]. Upon return, the first byte will
+ still contain the Report ID, and the report data will
+ start in data[1].
+
+ @ingroup API
+ @param device A device handle returned from hid_open().
+ @param data A buffer to put the read data into, including
+ the Report ID. Set the first byte of @p data[] to the
+ Report ID of the report to be read, or set it to zero
+ if your device does not use numbered reports.
+ @param length The number of bytes to read, including an
+ extra byte for the report ID. The buffer can be longer
+ than the actual report.
+
+ @returns
+ This function returns the number of bytes read plus
+ one for the report ID (which is still in the first
+ byte), or -1 on error.
+ */
+ int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *device, unsigned char *data, size_t length);
+
+ /** @brief Close a HID device.
+
+ @ingroup API
+ @param device A device handle returned from hid_open().
+ */
+ void HID_API_EXPORT HID_API_CALL hid_close(hid_device *device);
+
+ /** @brief Get The Manufacturer String from a HID device.
+
+ @ingroup API
+ @param device A device handle returned from hid_open().
+ @param string A wide string buffer to put the data into.
+ @param maxlen The length of the buffer in multiples of wchar_t.
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *device, wchar_t *string, size_t maxlen);
+
+ /** @brief Get The Product String from a HID device.
+
+ @ingroup API
+ @param device A device handle returned from hid_open().
+ @param string A wide string buffer to put the data into.
+ @param maxlen The length of the buffer in multiples of wchar_t.
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT_CALL hid_get_product_string(hid_device *device, wchar_t *string, size_t maxlen);
+
+ /** @brief Get The Serial Number String from a HID device.
+
+ @ingroup API
+ @param device A device handle returned from hid_open().
+ @param string A wide string buffer to put the data into.
+ @param maxlen The length of the buffer in multiples of wchar_t.
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *device, wchar_t *string, size_t maxlen);
+
+ /** @brief Get a string from a HID device, based on its string index.
+
+ @ingroup API
+ @param device A device handle returned from hid_open().
+ @param string_index The index of the string to get.
+ @param string A wide string buffer to put the data into.
+ @param maxlen The length of the buffer in multiples of wchar_t.
+
+ @returns
+ This function returns 0 on success and -1 on error.
+ */
+ int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *device, int string_index, wchar_t *string, size_t maxlen);
+
+ /** @brief Get a string describing the last error which occurred.
+
+ @ingroup API
+ @param device A device handle returned from hid_open().
+
+ @returns
+ This function returns a string containing the last error
+ which occurred or NULL if none has occurred.
+ */
+ HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/include/inttypes.h b/include/inttypes.h
new file mode 100644
index 0000000..de2cc83
--- /dev/null
+++ b/include/inttypes.h
@@ -0,0 +1,522 @@
+/* Copyright (c) 2004,2005,2007 Joerg Wunsch Copyright (c) 2005, Carlos Lamas All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the copyright holders nor the names of contributors may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
+
+/* $Id: inttypes.h 1766 2008-10-17 21:33:57Z arcanum $ */
+
+#ifndef __INTTYPES_H_
+#define __INTTYPES_H_
+
+#include <stdint.h>
+
+/** \file */
+/** \defgroup avr_inttypes <inttypes.h>: Integer Type conversions
+ \code #include <inttypes.h> \endcode
+
+ This header file includes the exact-width integer definitions from
+ <tt><stdint.h></tt>, and extends them with additional facilities
+ provided by the implementation.
+
+ Currently, the extensions include two additional integer types
+ that could hold a "far" pointer (i.e. a code pointer that can
+ address more than 64 KB), as well as standard names for all printf
+ and scanf formatting options that are supported by the \ref avr_stdio.
+ As the library does not support the full range of conversion
+ specifiers from ISO 9899:1999, only those conversions that are
+ actually implemented will be listed here.
+
+ The idea behind these conversion macros is that, for each of the
+ types defined by <stdint.h>, a macro will be supplied that portably
+ allows formatting an object of that type in printf() or scanf()
+ operations. Example:
+
+ \code
+ #include <inttypes.h>
+
+ uint8_t smallval;
+ int32_t longval;
+ ...
+ printf("The hexadecimal value of smallval is %" PRIx8
+ ", the decimal value of longval is %" PRId32 ".\n",
+ smallval, longval);
+ \endcode
+*/
+
+/** \name Far pointers for memory access >64K */
+
+/* @{ */
+/** \ingroup avr_inttypes
+ signed integer type that can hold a pointer > 64 KB */
+typedef int32_t int_farptr_t;
+
+/** \ingroup avr_inttypes
+ unsigned integer type that can hold a pointer > 64 KB */
+typedef uint32_t uint_farptr_t;
+
+/* @} */
+
+#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS)
+
+
+/** \name macros for printf and scanf format specifiers
+
+ For C++, these are only included if __STDC_LIMIT_MACROS
+ is defined before including <inttypes.h>.
+ */
+
+/* @{ */
+/** \ingroup avr_inttypes
+ decimal printf format for int8_t */
+#define PRId8 "d"
+/** \ingroup avr_inttypes
+ decimal printf format for int_least8_t */
+#define PRIdLEAST8 "d"
+/** \ingroup avr_inttypes
+ decimal printf format for int_fast8_t */
+#define PRIdFAST8 "d"
+
+/** \ingroup avr_inttypes
+ integer printf format for int8_t */
+#define PRIi8 "i"
+/** \ingroup avr_inttypes
+ integer printf format for int_least8_t */
+#define PRIiLEAST8 "i"
+/** \ingroup avr_inttypes
+ integer printf format for int_fast8_t */
+#define PRIiFAST8 "i"
+
+
+/** \ingroup avr_inttypes
+ decimal printf format for int16_t */
+#define PRId16 "d"
+/** \ingroup avr_inttypes
+ decimal printf format for int_least16_t */
+#define PRIdLEAST16 "d"
+/** \ingroup avr_inttypes
+ decimal printf format for int_fast16_t */
+#define PRIdFAST16 "d"
+
+/** \ingroup avr_inttypes
+ integer printf format for int16_t */
+#define PRIi16 "i"
+/** \ingroup avr_inttypes
+ integer printf format for int_least16_t */
+#define PRIiLEAST16 "i"
+/** \ingroup avr_inttypes
+ integer printf format for int_fast16_t */
+#define PRIiFAST16 "i"
+
+
+/** \ingroup avr_inttypes
+ decimal printf format for int32_t */
+#define PRId32 "ld"
+/** \ingroup avr_inttypes
+ decimal printf format for int_least32_t */
+#define PRIdLEAST32 "ld"
+/** \ingroup avr_inttypes
+ decimal printf format for int_fast32_t */
+#define PRIdFAST32 "ld"
+
+/** \ingroup avr_inttypes
+ integer printf format for int32_t */
+#define PRIi32 "li"
+/** \ingroup avr_inttypes
+ integer printf format for int_least32_t */
+#define PRIiLEAST32 "li"
+/** \ingroup avr_inttypes
+ integer printf format for int_fast32_t */
+#define PRIiFAST32 "li"
+
+
+#ifdef __avr_libc_does_not_implement_long_long_in_printf_or_scanf
+
+#define PRId64 "lld"
+#define PRIdLEAST64 "lld"
+#define PRIdFAST64 "lld"
+
+#define PRIi64 "lli"
+#define PRIiLEAST64 "lli"
+#define PRIiFAST64 "lli"
+
+
+#define PRIdMAX "lld"
+#define PRIiMAX "lli"
+
+#endif
+
+/** \ingroup avr_inttypes
+ decimal printf format for intptr_t */
+#define PRIdPTR PRId16
+/** \ingroup avr_inttypes
+ integer printf format for intptr_t */
+#define PRIiPTR PRIi16
+
+/** \ingroup avr_inttypes
+ octal printf format for uint8_t */
+#define PRIo8 "o"
+/** \ingroup avr_inttypes
+ octal printf format for uint_least8_t */
+#define PRIoLEAST8 "o"
+/** \ingroup avr_inttypes
+ octal printf format for uint_fast8_t */
+#define PRIoFAST8 "o"
+
+/** \ingroup avr_inttypes
+ decimal printf format for uint8_t */
+#define PRIu8 "u"
+/** \ingroup avr_inttypes
+ decimal printf format for uint_least8_t */
+#define PRIuLEAST8 "u"
+/** \ingroup avr_inttypes
+ decimal printf format for uint_fast8_t */
+#define PRIuFAST8 "u"
+
+/** \ingroup avr_inttypes
+ hexadecimal printf format for uint8_t */
+#define PRIx8 "x"
+/** \ingroup avr_inttypes
+ hexadecimal printf format for uint_least8_t */
+#define PRIxLEAST8 "x"
+/** \ingroup avr_inttypes
+ hexadecimal printf format for uint_fast8_t */
+#define PRIxFAST8 "x"
+
+/** \ingroup avr_inttypes
+ uppercase hexadecimal printf format for uint8_t */
+#define PRIX8 "X"
+/** \ingroup avr_inttypes
+ uppercase hexadecimal printf format for uint_least8_t */
+#define PRIXLEAST8 "X"
+/** \ingroup avr_inttypes
+ uppercase hexadecimal printf format for uint_fast8_t */
+#define PRIXFAST8 "X"
+
+
+/** \ingroup avr_inttypes
+ octal printf format for uint16_t */
+#define PRIo16 "o"
+/** \ingroup avr_inttypes
+ octal printf format for uint_least16_t */
+#define PRIoLEAST16 "o"
+/** \ingroup avr_inttypes
+ octal printf format for uint_fast16_t */
+#define PRIoFAST16 "o"
+
+/** \ingroup avr_inttypes
+ decimal printf format for uint16_t */
+#define PRIu16 "u"
+/** \ingroup avr_inttypes
+ decimal printf format for uint_least16_t */
+#define PRIuLEAST16 "u"
+/** \ingroup avr_inttypes
+ decimal printf format for uint_fast16_t */
+#define PRIuFAST16 "u"
+
+/** \ingroup avr_inttypes
+ hexadecimal printf format for uint16_t */
+#define PRIx16 "x"
+/** \ingroup avr_inttypes
+ hexadecimal printf format for uint_least16_t */
+#define PRIxLEAST16 "x"
+/** \ingroup avr_inttypes
+ hexadecimal printf format for uint_fast16_t */
+#define PRIxFAST16 "x"
+
+/** \ingroup avr_inttypes
+ uppercase hexadecimal printf format for uint16_t */
+#define PRIX16 "X"
+/** \ingroup avr_inttypes
+ uppercase hexadecimal printf format for uint_least16_t */
+#define PRIXLEAST16 "X"
+/** \ingroup avr_inttypes
+ uppercase hexadecimal printf format for uint_fast16_t */
+#define PRIXFAST16 "X"
+
+
+/** \ingroup avr_inttypes
+ octal printf format for uint32_t */
+#define PRIo32 "lo"
+/** \ingroup avr_inttypes
+ octal printf format for uint_least32_t */
+#define PRIoLEAST32 "lo"
+/** \ingroup avr_inttypes
+ octal printf format for uint_fast32_t */
+#define PRIoFAST32 "lo"
+
+/** \ingroup avr_inttypes
+ decimal printf format for uint32_t */
+#define PRIu32 "lu"
+/** \ingroup avr_inttypes
+ decimal printf format for uint_least32_t */
+#define PRIuLEAST32 "lu"
+/** \ingroup avr_inttypes
+ decimal printf format for uint_fast32_t */
+#define PRIuFAST32 "lu"
+
+/** \ingroup avr_inttypes
+ hexadecimal printf format for uint32_t */
+#define PRIx32 "lx"
+/** \ingroup avr_inttypes
+ hexadecimal printf format for uint_least32_t */
+#define PRIxLEAST32 "lx"
+/** \ingroup avr_inttypes
+ hexadecimal printf format for uint_fast32_t */
+#define PRIxFAST32 "lx"
+
+/** \ingroup avr_inttypes
+ uppercase hexadecimal printf format for uint32_t */
+#define PRIX32 "lX"
+/** \ingroup avr_inttypes
+ uppercase hexadecimal printf format for uint_least32_t */
+#define PRIXLEAST32 "lX"
+/** \ingroup avr_inttypes
+ uppercase hexadecimal printf format for uint_fast32_t */
+#define PRIXFAST32 "lX"
+
+
+#ifdef __avr_libc_does_not_implement_long_long_in_printf_or_scanf
+
+#define PRIo64 "llo"
+#define PRIoLEAST64 "llo"
+#define PRIoFAST64 "llo"
+
+#define PRIu64 "llu"
+#define PRIuLEAST64 "llu"
+#define PRIuFAST64 "llu"
+
+#define PRIx64 "llx"
+#define PRIxLEAST64 "llx"
+#define PRIxFAST64 "llx"
+
+#define PRIX64 "llX"
+#define PRIXLEAST64 "llX"
+#define PRIXFAST64 "llX"
+
+#define PRIoMAX "llo"
+#define PRIuMAX "llu"
+#define PRIxMAX "llx"
+#define PRIXMAX "llX"
+
+#endif
+
+/** \ingroup avr_inttypes
+ octal printf format for uintptr_t */
+#define PRIoPTR PRIo16
+/** \ingroup avr_inttypes
+ decimal printf format for uintptr_t */
+#define PRIuPTR PRIu16
+/** \ingroup avr_inttypes
+ hexadecimal printf format for uintptr_t */
+#define PRIxPTR PRIx16
+/** \ingroup avr_inttypes
+ uppercase hexadecimal printf format for uintptr_t */
+#define PRIXPTR PRIX16
+
+
+#ifdef __avr_libc_does_not_implement_hh_in_scanf
+
+#define SCNd8 "hhd"
+#define SCNdLEAST8 "hhd"
+#define SCNdFAST8 "hhd"
+
+#define SCNi8 "hhi"
+#define SCNiLEAST8 "hhi"
+#define SCNiFAST8 "hhi"
+
+#endif
+
+
+/** \ingroup avr_inttypes
+ decimal scanf format for int16_t */
+#define SCNd16 "d"
+/** \ingroup avr_inttypes
+ decimal scanf format for int_least16_t */
+#define SCNdLEAST16 "d"
+/** \ingroup avr_inttypes
+ decimal scanf format for int_fast16_t */
+#define SCNdFAST16 "d"
+
+/** \ingroup avr_inttypes
+ generic-integer scanf format for int16_t */
+#define SCNi16 "i"
+/** \ingroup avr_inttypes
+ generic-integer scanf format for int_least16_t */
+#define SCNiLEAST16 "i"
+/** \ingroup avr_inttypes
+ generic-integer scanf format for int_fast16_t */
+#define SCNiFAST16 "i"
+
+
+/** \ingroup avr_inttypes
+ decimal scanf format for int32_t */
+#define SCNd32 "ld"
+/** \ingroup avr_inttypes
+ decimal scanf format for int_least32_t */
+#define SCNdLEAST32 "ld"
+/** \ingroup avr_inttypes
+ decimal scanf format for int_fast32_t */
+#define SCNdFAST32 "ld"
+
+/** \ingroup avr_inttypes
+ generic-integer scanf format for int32_t */
+#define SCNi32 "li"
+/** \ingroup avr_inttypes
+ generic-integer scanf format for int_least32_t */
+#define SCNiLEAST32 "li"
+/** \ingroup avr_inttypes
+ generic-integer scanf format for int_fast32_t */
+#define SCNiFAST32 "li"
+
+
+#ifdef __avr_libc_does_not_implement_long_long_in_printf_or_scanf
+
+#define SCNd64 "lld"
+#define SCNdLEAST64 "lld"
+#define SCNdFAST64 "lld"
+
+#define SCNi64 "lli"
+#define SCNiLEAST64 "lli"
+#define SCNiFAST64 "lli"
+
+#define SCNdMAX "lld"
+#define SCNiMAX "lli"
+
+#endif
+
+/** \ingroup avr_inttypes
+ decimal scanf format for intptr_t */
+#define SCNdPTR SCNd16
+/** \ingroup avr_inttypes
+ generic-integer scanf format for intptr_t */
+#define SCNiPTR SCNi16
+
+#ifdef __avr_libc_does_not_implement_hh_in_scanf
+
+#define SCNo8 "hho"
+#define SCNoLEAST8 "hho"
+#define SCNoFAST8 "hho"
+
+#define SCNu8 "hhu"
+#define SCNuLEAST8 "hhu"
+#define SCNuFAST8 "hhu"
+
+#define SCNx8 "hhx"
+#define SCNxLEAST8 "hhx"
+#define SCNxFAST8 "hhx"
+
+#endif
+
+/** \ingroup avr_inttypes
+ octal scanf format for uint16_t */
+#define SCNo16 "o"
+/** \ingroup avr_inttypes
+ octal scanf format for uint_least16_t */
+#define SCNoLEAST16 "o"
+/** \ingroup avr_inttypes
+ octal scanf format for uint_fast16_t */
+#define SCNoFAST16 "o"
+
+/** \ingroup avr_inttypes
+ decimal scanf format for uint16_t */
+#define SCNu16 "u"
+/** \ingroup avr_inttypes
+ decimal scanf format for uint_least16_t */
+#define SCNuLEAST16 "u"
+/** \ingroup avr_inttypes
+ decimal scanf format for uint_fast16_t */
+#define SCNuFAST16 "u"
+
+/** \ingroup avr_inttypes
+ hexadecimal scanf format for uint16_t */
+#define SCNx16 "x"
+/** \ingroup avr_inttypes
+ hexadecimal scanf format for uint_least16_t */
+#define SCNxLEAST16 "x"
+/** \ingroup avr_inttypes
+ hexadecimal scanf format for uint_fast16_t */
+#define SCNxFAST16 "x"
+
+
+/** \ingroup avr_inttypes
+ octal scanf format for uint32_t */
+#define SCNo32 "lo"
+/** \ingroup avr_inttypes
+ octal scanf format for uint_least32_t */
+#define SCNoLEAST32 "lo"
+/** \ingroup avr_inttypes
+ octal scanf format for uint_fast32_t */
+#define SCNoFAST32 "lo"
+
+/** \ingroup avr_inttypes
+ decimal scanf format for uint32_t */
+#define SCNu32 "lu"
+/** \ingroup avr_inttypes
+ decimal scanf format for uint_least32_t */
+#define SCNuLEAST32 "lu"
+/** \ingroup avr_inttypes
+ decimal scanf format for uint_fast32_t */
+#define SCNuFAST32 "lu"
+
+/** \ingroup avr_inttypes
+ hexadecimal scanf format for uint32_t */
+#define SCNx32 "lx"
+/** \ingroup avr_inttypes
+ hexadecimal scanf format for uint_least32_t */
+#define SCNxLEAST32 "lx"
+/** \ingroup avr_inttypes
+ hexadecimal scanf format for uint_fast32_t */
+#define SCNxFAST32 "lx"
+
+
+#ifdef __avr_libc_does_not_implement_long_long_in_printf_or_scanf
+
+#define SCNo64 "llo"
+#define SCNoLEAST64 "llo"
+#define SCNoFAST64 "llo"
+
+#define SCNu64 "llu"
+#define SCNuLEAST64 "llu"
+#define SCNuFAST64 "llu"
+
+#define SCNx64 "llx"
+#define SCNxLEAST64 "llx"
+#define SCNxFAST64 "llx"
+
+#define SCNoMAX "llo"
+#define SCNuMAX "llu"
+#define SCNxMAX "llx"
+
+#endif
+
+/** \ingroup avr_inttypes
+ octal scanf format for uintptr_t */
+#define SCNoPTR SCNo16
+/** \ingroup avr_inttypes
+ decimal scanf format for uintptr_t */
+#define SCNuPTR SCNu16
+/** \ingroup avr_inttypes
+ hexadecimal scanf format for uintptr_t */
+#define SCNxPTR SCNx16
+
+/* @} */
+
+
+#endif /* !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) */
+
+
+#endif /* __INTTYPES_H_ */
diff --git a/include/log.h b/include/log.h
new file mode 100644
index 0000000..a97ff25
--- /dev/null
+++ b/include/log.h
@@ -0,0 +1,83 @@
+#ifndef LOG_H
+#define LOG_H
+
+#include <string>
+#include <cstddef>
+
+#include <functional>
+
+namespace nitrokey {
+ namespace log {
+
+//for MSVC
+#ifdef ERROR
+#undef ERROR
+#endif
+
+
+ enum class Loglevel : int {
+ ERROR,
+ WARNING,
+ INFO,
+ DEBUG_L1,
+ DEBUG,
+ DEBUG_L2
+ };
+
+ class LogHandler {
+ public:
+ virtual void print(const std::string &, Loglevel lvl) = 0;
+ protected:
+ std::string loglevel_to_str(Loglevel);
+ std::string format_message_to_string(const std::string &str, const Loglevel &lvl);
+
+ };
+
+ class StdlogHandler : public LogHandler {
+ public:
+ virtual void print(const std::string &, Loglevel lvl);
+ };
+
+ class FunctionalLogHandler : public LogHandler {
+ using log_function_type = std::function<void(std::string)>;
+ log_function_type log_function;
+ public:
+ FunctionalLogHandler(log_function_type _log_function);
+ virtual void print(const std::string &, Loglevel lvl);
+
+ };
+
+ extern StdlogHandler stdlog_handler;
+
+ class Log {
+ public:
+ Log() : mp_loghandler(&stdlog_handler), m_loglevel(Loglevel::WARNING) {}
+
+ static Log &instance() {
+ if (mp_instance == nullptr) mp_instance = new Log;
+ return *mp_instance;
+ }
+
+ void operator()(const std::string &, Loglevel);
+ void set_loglevel(Loglevel lvl) { m_loglevel = lvl; }
+ void set_handler(LogHandler *handler) { mp_loghandler = handler; }
+
+ private:
+ LogHandler *mp_loghandler;
+ Loglevel m_loglevel;
+
+ static Log *mp_instance;
+ };
+ }
+}
+
+
+#ifdef NO_LOG
+#define LOG(string, level) while(false){}
+#define LOGD(string) while(false){}
+#else
+#define LOG(string, level) nitrokey::log::Log::instance()((string), (level))
+#define LOGD(string) nitrokey::log::Log::instance()((string), (nitrokey::log::Loglevel::DEBUG_L2))
+#endif
+
+#endif
diff --git a/include/misc.h b/include/misc.h
new file mode 100644
index 0000000..25f3107
--- /dev/null
+++ b/include/misc.h
@@ -0,0 +1,72 @@
+#ifndef MISC_H
+#define MISC_H
+#include <stdio.h>
+#include <string>
+#include <vector>
+#include <string.h>
+#include "log.h"
+#include "LibraryException.h"
+#include <sstream>
+#include <iomanip>
+
+
+namespace nitrokey {
+namespace misc {
+
+ template<typename T>
+ std::string toHex(T value){
+ using namespace std;
+ std::ostringstream oss;
+ oss << std::hex << std::setw(sizeof(value)*2) << std::setfill('0') << value;
+ return oss.str();
+ }
+
+ /**
+ * Copies string from pointer to fixed size C-style array. Src needs to be a valid C-string - eg. ended with '\0'.
+ * Throws when source is bigger than destination.
+ * @tparam T type of destination array
+ * @param dest fixed size destination array
+ * @param src pointer to source c-style valid string
+ */
+ template <typename T>
+ void strcpyT(T& dest, const char* src){
+
+ if (src == nullptr)
+// throw EmptySourceStringException(slot_number);
+ return;
+ const size_t s_dest = sizeof dest;
+ LOG(std::string("strcpyT sizes dest src ")
+ +std::to_string(s_dest)+ " "
+ +std::to_string(strlen(src))+ " "
+ ,nitrokey::log::Loglevel::DEBUG_L2);
+ if (strlen(src) > s_dest){
+ throw TooLongStringException(strlen(src), s_dest, src);
+ }
+ strncpy((char*) &dest, src, s_dest);
+ }
+
+#define bzero(b,len) (memset((b), '\0', (len)), (void) 0)
+ template <typename T>
+typename T::CommandPayload get_payload(){
+ //Create, initialize and return by value command payload
+ typename T::CommandPayload st;
+ bzero(&st, sizeof(st));
+ return st;
+}
+
+ template<typename CMDTYPE, typename Tdev>
+ void execute_password_command(Tdev &stick, const char *password) {
+ auto p = get_payload<CMDTYPE>();
+ p.set_defaults();
+ strcpyT(p.password, password);
+ CMDTYPE::CommandTransaction::run(stick, p);
+ }
+
+ std::string hexdump(const uint8_t *p, size_t size, bool print_header=true, bool print_ascii=true,
+ bool print_empty=true);
+ uint32_t stm_crc32(const uint8_t *data, size_t size);
+ std::vector<uint8_t> hex_string_to_byte(const char* hexString);
+}
+}
+
+#endif
diff --git a/include/stick10_commands.h b/include/stick10_commands.h
new file mode 100644
index 0000000..8f3ceef
--- /dev/null
+++ b/include/stick10_commands.h
@@ -0,0 +1,857 @@
+#ifndef STICK10_COMMANDS_H
+#define STICK10_COMMANDS_H
+
+#include <bitset>
+#include <iomanip>
+#include <string>
+#include <sstream>
+#include <stdint.h>
+#include "device_proto.h"
+#include "command.h"
+
+#pragma pack (push,1)
+
+namespace nitrokey {
+namespace proto {
+
+
+
+/*
+ * Stick10 protocol definition
+ */
+namespace stick10 {
+class GetSlotName : public Command<CommandID::READ_SLOT_NAME> {
+ public:
+ // reachable as a typedef in Transaction
+ struct CommandPayload {
+ uint8_t slot_number;
+
+ bool isValid() const { return slot_number<0x10+3; }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "slot_number:\t" << (int)(slot_number) << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ struct ResponsePayload {
+ uint8_t slot_name[15];
+
+ bool isValid() const { return true; }
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss_volatile(slot_name);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload,
+ struct ResponsePayload> CommandTransaction;
+};
+
+class EraseSlot : Command<CommandID::ERASE_SLOT> {
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+
+ bool isValid() const { return !(slot_number & 0xF0); }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "slot_number:\t" << (int)(slot_number) << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+class SetTime : Command<CommandID::SET_TIME> {
+ public:
+ struct CommandPayload {
+ uint8_t reset; // 0 - get time, 1 - set time
+ uint64_t time; // posix time
+
+ bool isValid() const { return reset && reset != 1; }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "reset:\t" << (int)(reset) << std::endl;
+ ss << "time:\t" << (time) << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+
+class WriteToHOTPSlot : Command<CommandID::WRITE_TO_SLOT> {
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+ uint8_t slot_name[15];
+ uint8_t slot_secret[20];
+ union{
+ uint8_t _slot_config;
+ struct{
+ bool use_8_digits : 1;
+ bool use_enter : 1;
+ bool use_tokenID : 1;
+ };
+ };
+ union{
+ uint8_t slot_token_id[13]; /** OATH Token Identifier */
+ struct{ /** @see https://openauthentication.org/token-specs/ */
+ uint8_t omp[2];
+ uint8_t tt[2];
+ uint8_t mui[8];
+ uint8_t keyboard_layout; //disabled feature in nitroapp as of 20160805
+ } slot_token_fields;
+ };
+ union{
+ uint64_t slot_counter;
+ uint8_t slot_counter_s[8];
+ } __packed;
+
+ bool isValid() const { return !(slot_number & 0xF0); }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "slot_number:\t" << (int)(slot_number) << std::endl;
+ print_to_ss_volatile(slot_name);
+ print_to_ss_volatile(slot_secret);
+ ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl;
+ ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl;
+ ss << "\tuse_enter(1):\t" << use_enter << std::endl;
+ ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl;
+
+ ss << "slot_token_id:\t";
+ for (auto i : slot_token_id)
+ ss << std::hex << std::setw(2) << std::setfill('0')<< (int) i << " " ;
+ ss << std::endl;
+ ss << "slot_counter:\t[" << (int)slot_counter << "]\t"
+ << ::nitrokey::misc::hexdump((const uint8_t *)(&slot_counter), sizeof slot_counter, false);
+
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+class WriteToTOTPSlot : Command<CommandID::WRITE_TO_SLOT> {
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+ uint8_t slot_name[15];
+ uint8_t slot_secret[20];
+ union{
+ uint8_t _slot_config;
+ struct{
+ bool use_8_digits : 1;
+ bool use_enter : 1;
+ bool use_tokenID : 1;
+ };
+ };
+ union{
+ uint8_t slot_token_id[13]; /** OATH Token Identifier */
+ struct{ /** @see https://openauthentication.org/token-specs/ */
+ uint8_t omp[2];
+ uint8_t tt[2];
+ uint8_t mui[8];
+ uint8_t keyboard_layout; //disabled feature in nitroapp as of 20160805
+ } slot_token_fields;
+ };
+ uint16_t slot_interval;
+
+ bool isValid() const { return !(slot_number & 0xF0); } //TODO check
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "slot_number:\t" << (int)(slot_number) << std::endl;
+ print_to_ss_volatile(slot_name);
+ print_to_ss_volatile(slot_secret);
+ ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl;
+ ss << "slot_token_id:\t";
+ for (auto i : slot_token_id)
+ ss << std::hex << std::setw(2) << std::setfill('0')<< (int) i << " " ;
+ ss << std::endl;
+ ss << "slot_interval:\t" << (int)slot_interval << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+class GetTOTP : Command<CommandID::GET_CODE> {
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+ uint64_t challenge;
+ uint64_t last_totp_time;
+ uint8_t last_interval;
+
+ bool isValid() const { return !(slot_number & 0xF0); }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "slot_number:\t" << (int)(slot_number) << std::endl;
+ ss << "challenge:\t" << (challenge) << std::endl;
+ ss << "last_totp_time:\t" << (last_totp_time) << std::endl;
+ ss << "last_interval:\t" << (int)(last_interval) << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ struct ResponsePayload {
+ union {
+ uint8_t whole_response[18]; //14 bytes reserved for config, but used only 1
+ struct {
+ uint32_t code;
+ union{
+ uint8_t _slot_config;
+ struct{
+ bool use_8_digits : 1;
+ bool use_enter : 1;
+ bool use_tokenID : 1;
+ };
+ };
+ } __packed ;
+ } __packed ;
+
+ bool isValid() const { return true; }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "code:\t" << (code) << std::endl;
+ ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl;
+ ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl;
+ ss << "\tuse_enter(1):\t" << use_enter << std::endl;
+ ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct ResponsePayload>
+ CommandTransaction;
+};
+
+class GetHOTP : Command<CommandID::GET_CODE> {
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+
+ bool isValid() const { return (slot_number & 0xF0); }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "slot_number:\t" << (int)(slot_number) << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ struct ResponsePayload {
+ union {
+ uint8_t whole_response[18]; //14 bytes reserved for config, but used only 1
+ struct {
+ uint32_t code;
+ union{
+ uint8_t _slot_config;
+ struct{
+ bool use_8_digits : 1;
+ bool use_enter : 1;
+ bool use_tokenID : 1;
+ };
+ };
+ } __packed;
+ } __packed;
+
+ bool isValid() const { return true; }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "code:\t" << (code) << std::endl;
+ ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl;
+ ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl;
+ ss << "\tuse_enter(1):\t" << use_enter << std::endl;
+ ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct ResponsePayload>
+ CommandTransaction;
+};
+
+class ReadSlot : Command<CommandID::READ_SLOT> {
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+
+ bool isValid() const { return !(slot_number & 0xF0); }
+
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "slot_number:\t" << (int)(slot_number) << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ struct ResponsePayload {
+ uint8_t slot_name[15];
+ union{
+ uint8_t _slot_config;
+ struct{
+ bool use_8_digits : 1;
+ bool use_enter : 1;
+ bool use_tokenID : 1;
+ };
+ };
+ union{
+ uint8_t slot_token_id[13]; /** OATH Token Identifier */
+ struct{ /** @see https://openauthentication.org/token-specs/ */
+ uint8_t omp[2];
+ uint8_t tt[2];
+ uint8_t mui[8];
+ uint8_t keyboard_layout; //disabled feature in nitroapp as of 20160805
+ } slot_token_fields;
+ };
+ union{
+ uint64_t slot_counter;
+ uint8_t slot_counter_s[8];
+ } __packed;
+
+ bool isValid() const { return true; }
+
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss_volatile(slot_name);
+ ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl;
+ ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl;
+ ss << "\tuse_enter(1):\t" << use_enter << std::endl;
+ ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl;
+
+ ss << "slot_token_id:\t";
+ for (auto i : slot_token_id)
+ ss << std::hex << std::setw(2) << std::setfill('0')<< (int) i << " " ;
+ ss << std::endl;
+ ss << "slot_counter:\t[" << (int)slot_counter << "]\t"
+ << ::nitrokey::misc::hexdump((const uint8_t *)(&slot_counter), sizeof slot_counter, false);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload,
+ struct ResponsePayload> CommandTransaction;
+};
+
+class GetStatus : Command<CommandID::GET_STATUS> {
+ public:
+ struct ResponsePayload {
+ uint16_t firmware_version;
+ union{
+ uint8_t card_serial[4];
+ uint32_t card_serial_u32;
+ } __packed;
+ union {
+ uint8_t general_config[5];
+ struct{
+ uint8_t numlock; /** 0-1: HOTP slot number from which the code will be get on double press, other value - function disabled */
+ uint8_t capslock; /** same as numlock */
+ uint8_t scrolllock; /** same as numlock */
+ uint8_t enable_user_password;
+ uint8_t delete_user_password;
+ } __packed;
+ } __packed;
+ bool isValid() const { return enable_user_password!=delete_user_password; }
+
+ std::string get_card_serial_hex() const {
+ return nitrokey::misc::toHex(card_serial_u32);
+ }
+
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "firmware_version:\t"
+ << "[" << firmware_version << "]" << "\t"
+ << ::nitrokey::misc::hexdump(
+ (const uint8_t *)(&firmware_version), sizeof firmware_version, false);
+ ss << "card_serial_u32:\t" << std::hex << card_serial_u32 << std::endl;
+ ss << "card_serial:\t"
+ << ::nitrokey::misc::hexdump((const uint8_t *)(card_serial),
+ sizeof card_serial, false);
+ ss << "general_config:\t"
+ << ::nitrokey::misc::hexdump((const uint8_t *)(general_config),
+ sizeof general_config, false);
+ ss << "numlock:\t" << (int)numlock << std::endl;
+ ss << "capslock:\t" << (int)capslock << std::endl;
+ ss << "scrolllock:\t" << (int)scrolllock << std::endl;
+ ss << "enable_user_password:\t" << (bool) enable_user_password << std::endl;
+ ss << "delete_user_password:\t" << (bool) delete_user_password << std::endl;
+
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct EmptyPayload, struct ResponsePayload>
+ CommandTransaction;
+};
+
+class GetPasswordRetryCount : Command<CommandID::GET_PASSWORD_RETRY_COUNT> {
+ public:
+ struct ResponsePayload {
+ uint8_t password_retry_count;
+
+ bool isValid() const { return true; }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << " password_retry_count\t" << (int)password_retry_count << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct EmptyPayload, struct ResponsePayload>
+ CommandTransaction;
+};
+
+class GetUserPasswordRetryCount
+ : Command<CommandID::GET_USER_PASSWORD_RETRY_COUNT> {
+ public:
+ struct ResponsePayload {
+ uint8_t password_retry_count;
+
+ bool isValid() const { return true; }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << " password_retry_count\t" << (int)password_retry_count << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct EmptyPayload, struct ResponsePayload>
+ CommandTransaction;
+};
+
+ template <typename T, typename Q, int N>
+ void write_array(T &ss, Q (&arr)[N]){
+ for (int i=0; i<N; i++){
+ ss << std::hex << std::setfill('0') << std::setw(2) << (int)arr[i] << " ";
+ }
+ ss << std::endl;
+ };
+
+
+class GetPasswordSafeSlotStatus : Command<CommandID::GET_PW_SAFE_SLOT_STATUS> {
+ public:
+ struct ResponsePayload {
+ uint8_t password_safe_status[PWS_SLOT_COUNT];
+
+ bool isValid() const { return true; }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "password_safe_status\t";
+ write_array(ss, password_safe_status);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct EmptyPayload, struct ResponsePayload>
+ CommandTransaction;
+};
+
+class GetPasswordSafeSlotName : Command<CommandID::GET_PW_SAFE_SLOT_NAME> {
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+
+ bool isValid() const { return !(slot_number & 0xF0); }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "slot_number\t" << (int)slot_number << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ struct ResponsePayload {
+ uint8_t slot_name[PWS_SLOTNAME_LENGTH];
+
+ bool isValid() const { return true; }
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss_volatile(slot_name);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload,
+ struct ResponsePayload> CommandTransaction;
+};
+
+class GetPasswordSafeSlotPassword
+ : Command<CommandID::GET_PW_SAFE_SLOT_PASSWORD> {
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+
+ bool isValid() const { return !(slot_number & 0xF0); }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << " slot_number\t" << (int)slot_number << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ struct ResponsePayload {
+ uint8_t slot_password[PWS_PASSWORD_LENGTH];
+
+ bool isValid() const { return true; }
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss_volatile(slot_password);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload,
+ struct ResponsePayload> CommandTransaction;
+};
+
+class GetPasswordSafeSlotLogin
+ : Command<CommandID::GET_PW_SAFE_SLOT_LOGINNAME> {
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+
+ bool isValid() const { return !(slot_number & 0xF0); }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << " slot_number\t" << (int)slot_number << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ struct ResponsePayload {
+ uint8_t slot_login[PWS_LOGINNAME_LENGTH];
+
+ bool isValid() const { return true; }
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss_volatile(slot_login);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload,
+ struct ResponsePayload> CommandTransaction;
+};
+
+class SetPasswordSafeSlotData : Command<CommandID::SET_PW_SAFE_SLOT_DATA_1> {
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+ uint8_t slot_name[PWS_SLOTNAME_LENGTH];
+ uint8_t slot_password[PWS_PASSWORD_LENGTH];
+
+ bool isValid() const { return !(slot_number & 0xF0); }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << " slot_number\t" << (int)slot_number << std::endl;
+ print_to_ss_volatile(slot_name);
+ print_to_ss_volatile(slot_password);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+class SetPasswordSafeSlotData2 : Command<CommandID::SET_PW_SAFE_SLOT_DATA_2> {
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+ uint8_t slot_login_name[PWS_LOGINNAME_LENGTH];
+
+ bool isValid() const { return !(slot_number & 0xF0); }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << " slot_number\t" << (int)slot_number << std::endl;
+ print_to_ss_volatile(slot_login_name);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+class ErasePasswordSafeSlot : Command<CommandID::PW_SAFE_ERASE_SLOT> {
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+
+ bool isValid() const { return !(slot_number & 0xF0); }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << " slot_number\t" << (int)slot_number << std::endl;
+ return ss.str();
+ }
+
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+class EnablePasswordSafe : Command<CommandID::PW_SAFE_ENABLE> {
+ public:
+ struct CommandPayload {
+ uint8_t user_password[30];
+
+ bool isValid() const { return true; }
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss_volatile(user_password);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+class PasswordSafeInitKey : Command<CommandID::PW_SAFE_INIT_KEY> {
+ /**
+ * never used in Nitrokey App
+ */
+ public:
+ typedef Transaction<command_id(), struct EmptyPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+class PasswordSafeSendSlotViaHID : Command<CommandID::PW_SAFE_SEND_DATA> {
+ /**
+ * never used in Nitrokey App
+ */
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+ uint8_t slot_kind;
+
+ bool isValid() const { return !(slot_number & 0xF0); }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+// TODO "Device::passwordSafeSendSlotDataViaHID"
+
+class WriteGeneralConfig : Command<CommandID::WRITE_CONFIG> {
+ public:
+ struct CommandPayload {
+ union{
+ uint8_t config[5];
+ struct{
+ uint8_t numlock; /** 0-1: HOTP slot number from which the code will be get on double press, other value - function disabled */
+ uint8_t capslock; /** same as numlock */
+ uint8_t scrolllock; /** same as numlock */
+ uint8_t enable_user_password;
+ uint8_t delete_user_password;
+ };
+ };
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "numlock:\t" << (int)numlock << std::endl;
+ ss << "capslock:\t" << (int)capslock << std::endl;
+ ss << "scrolllock:\t" << (int)scrolllock << std::endl;
+ ss << "enable_user_password:\t" << (bool) enable_user_password << std::endl;
+ ss << "delete_user_password:\t" << (bool) delete_user_password << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+class FirstAuthenticate : Command<CommandID::FIRST_AUTHENTICATE> {
+ public:
+ struct CommandPayload {
+ uint8_t card_password[25];
+ uint8_t temporary_password[25];
+
+ bool isValid() const { return true; }
+
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss_volatile(card_password);
+ hexdump_to_ss(temporary_password);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+class UserAuthenticate : Command<CommandID::USER_AUTHENTICATE> {
+ public:
+ struct CommandPayload {
+ uint8_t card_password[25];
+ uint8_t temporary_password[25];
+
+ bool isValid() const { return true; }
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss_volatile(card_password);
+ hexdump_to_ss(temporary_password);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+class Authorize : Command<CommandID::AUTHORIZE> {
+ public:
+ struct CommandPayload {
+ uint32_t crc_to_authorize;
+ uint8_t temporary_password[25];
+
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << " crc_to_authorize:\t" << std::hex << std::setw(2) << std::setfill('0') << crc_to_authorize<< std::endl;
+ hexdump_to_ss(temporary_password);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+class UserAuthorize : Command<CommandID::USER_AUTHORIZE> {
+ public:
+ struct CommandPayload {
+ uint32_t crc_to_authorize;
+ uint8_t temporary_password[25];
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << " crc_to_authorize:\t" << crc_to_authorize<< std::endl;
+ hexdump_to_ss(temporary_password);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+class UnlockUserPassword : Command<CommandID::UNLOCK_USER_PASSWORD> {
+ public:
+ struct CommandPayload {
+ uint8_t admin_password[25];
+ uint8_t user_new_password[25];
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss_volatile(admin_password);
+ print_to_ss_volatile(user_new_password);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+class ChangeUserPin : Command<CommandID::CHANGE_USER_PIN> {
+ public:
+ struct CommandPayload {
+ uint8_t old_pin[25];
+ uint8_t new_pin[25];
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss_volatile(old_pin);
+ print_to_ss_volatile(new_pin);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+class IsAESSupported : Command<CommandID::DETECT_SC_AES> {
+ public:
+ struct CommandPayload {
+ uint8_t user_password[20];
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss_volatile(user_password);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+
+class ChangeAdminPin : Command<CommandID::CHANGE_ADMIN_PIN> {
+ public:
+ struct CommandPayload {
+ uint8_t old_pin[25];
+ uint8_t new_pin[25];
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss_volatile(old_pin);
+ print_to_ss_volatile(new_pin);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+class LockDevice : Command<CommandID::LOCK_DEVICE> {
+ public:
+ typedef Transaction<command_id(), struct EmptyPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+class FactoryReset : Command<CommandID::FACTORY_RESET> {
+ public:
+ struct CommandPayload {
+ uint8_t admin_password[20];
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss_volatile(admin_password);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+};
+
+class BuildAESKey : Command<CommandID::NEW_AES_KEY> {
+ public:
+ struct CommandPayload {
+ uint8_t admin_password[20];
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss_volatile(admin_password);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+
+};
+
+}
+}
+}
+#pragma pack (pop)
+#endif
diff --git a/include/stick10_commands_0.8.h b/include/stick10_commands_0.8.h
new file mode 100644
index 0000000..361682d
--- /dev/null
+++ b/include/stick10_commands_0.8.h
@@ -0,0 +1,326 @@
+//
+// Created by sz on 08.11.16.
+//
+
+#ifndef LIBNITROKEY_STICK10_COMMANDS_0_8_H
+#define LIBNITROKEY_STICK10_COMMANDS_0_8_H
+
+#include <bitset>
+#include <iomanip>
+#include <string>
+#include <sstream>
+#include <cstdint>
+#include "command.h"
+#include "device_proto.h"
+#include "stick10_commands.h"
+
+#pragma pack (push,1)
+
+
+namespace nitrokey {
+ namespace proto {
+
+/*
+ * Stick10 protocol definition
+ */
+ namespace stick10_08 {
+ using stick10::FirstAuthenticate;
+ using stick10::UserAuthenticate;
+ using stick10::SetTime;
+ using stick10::GetStatus;
+ using stick10::BuildAESKey;
+ using stick10::ChangeAdminPin;
+ using stick10::ChangeUserPin;
+ using stick10::EnablePasswordSafe;
+ using stick10::ErasePasswordSafeSlot;
+ using stick10::FactoryReset;
+ using stick10::GetPasswordRetryCount;
+ using stick10::GetUserPasswordRetryCount;
+ using stick10::GetPasswordSafeSlotLogin;
+ using stick10::GetPasswordSafeSlotName;
+ using stick10::GetPasswordSafeSlotPassword;
+ using stick10::GetPasswordSafeSlotStatus;
+ using stick10::GetSlotName;
+ using stick10::IsAESSupported;
+ using stick10::LockDevice;
+ using stick10::PasswordSafeInitKey;
+ using stick10::PasswordSafeSendSlotViaHID;
+ using stick10::SetPasswordSafeSlotData;
+ using stick10::SetPasswordSafeSlotData2;
+ using stick10::UnlockUserPassword;
+ using stick10::ReadSlot;
+
+ class EraseSlot : Command<CommandID::ERASE_SLOT> {
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+ uint8_t temporary_admin_password[25];
+
+ bool isValid() const { return !(slot_number & 0xF0); }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "slot_number:\t" << (int)(slot_number) << std::endl;
+ hexdump_to_ss(temporary_admin_password);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+ };
+
+ class SendOTPData : Command<CommandID::SEND_OTP_DATA> {
+ //admin auth
+ public:
+ struct CommandPayload {
+ uint8_t temporary_admin_password[25];
+ uint8_t type; //S-secret, N-name
+ uint8_t id; //multiple reports for values longer than 30 bytes
+ uint8_t data[30]; //data, does not need null termination
+
+ bool isValid() const { return true; }
+
+ void setTypeName(){
+ type = 'N';
+ }
+ void setTypeSecret(){
+ type = 'S';
+ }
+
+ std::string dissect() const {
+ std::stringstream ss;
+ hexdump_to_ss(temporary_admin_password);
+ ss << "type:\t" << type << std::endl;
+ ss << "id:\t" << (int)id << std::endl;
+#ifdef LOG_VOLATILE_DATA
+ ss << "data:" << std::endl
+ << ::nitrokey::misc::hexdump((const uint8_t *) (&data), sizeof data);
+#else
+ ss << " Volatile data not logged" << std::endl;
+#endif
+ return ss.str();
+ }
+ } __packed;
+
+
+ struct ResponsePayload {
+ union {
+ uint8_t data[40];
+ } __packed;
+
+ bool isValid() const { return true; }
+ std::string dissect() const {
+ std::stringstream ss;
+#ifdef LOG_VOLATILE_DATA
+ ss << "data:" << std::endl
+ << ::nitrokey::misc::hexdump((const uint8_t *) (&data), sizeof data);
+#else
+ ss << " Volatile data not logged" << std::endl;
+#endif
+ return ss.str();
+ }
+ } __packed;
+
+
+ typedef Transaction<command_id(), struct CommandPayload, struct ResponsePayload>
+ CommandTransaction;
+ };
+
+ class WriteToOTPSlot : Command<CommandID::WRITE_TO_SLOT> {
+ //admin auth
+ public:
+ struct CommandPayload {
+ uint8_t temporary_admin_password[25];
+ uint8_t slot_number;
+ union {
+ uint64_t slot_counter_or_interval;
+ uint8_t slot_counter_s[8];
+ } __packed;
+ union {
+ uint8_t _slot_config;
+ struct {
+ bool use_8_digits : 1;
+ bool use_enter : 1;
+ bool use_tokenID : 1;
+ };
+ };
+ union {
+ uint8_t slot_token_id[13]; /** OATH Token Identifier */
+ struct { /** @see https://openauthentication.org/token-specs/ */
+ uint8_t omp[2];
+ uint8_t tt[2];
+ uint8_t mui[8];
+ uint8_t keyboard_layout; //disabled feature in nitroapp as of 20160805
+ } slot_token_fields;
+ };
+
+ bool isValid() const { return true; }
+
+ std::string dissect() const {
+ std::stringstream ss;
+ hexdump_to_ss(temporary_admin_password);
+ ss << "slot_config:\t" << std::bitset<8>((int) _slot_config) << std::endl;
+ ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl;
+ ss << "\tuse_enter(1):\t" << use_enter << std::endl;
+ ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl;
+ ss << "slot_number:\t" << (int) (slot_number) << std::endl;
+ ss << "slot_counter_or_interval:\t[" << (int) slot_counter_or_interval << "]\t"
+ << ::nitrokey::misc::hexdump((const uint8_t *) (&slot_counter_or_interval), sizeof slot_counter_or_interval, false);
+
+ ss << "slot_token_id:\t";
+ for (auto i : slot_token_id)
+ ss << std::hex << std::setw(2) << std::setfill('0') << (int) i << " ";
+ ss << std::endl;
+
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+ };
+
+ class GetHOTP : Command<CommandID::GET_CODE> {
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+ struct {
+ uint64_t challenge; //@unused
+ uint64_t last_totp_time; //@unused
+ uint8_t last_interval; //@unused
+ } __packed _unused;
+ uint8_t temporary_user_password[25];
+
+ bool isValid() const { return (slot_number & 0xF0); }
+ std::string dissect() const {
+ std::stringstream ss;
+ hexdump_to_ss(temporary_user_password);
+ ss << "slot_number:\t" << (int)(slot_number) << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ struct ResponsePayload {
+ union {
+ uint8_t whole_response[18]; //14 bytes reserved for config, but used only 1
+ struct {
+ uint32_t code;
+ union{
+ uint8_t _slot_config;
+ struct{
+ bool use_8_digits : 1;
+ bool use_enter : 1;
+ bool use_tokenID : 1;
+ };
+ };
+ } __packed;
+ } __packed;
+
+ bool isValid() const { return true; }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "code:\t" << (code) << std::endl;
+ ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl;
+ ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl;
+ ss << "\tuse_enter(1):\t" << use_enter << std::endl;
+ ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct ResponsePayload>
+ CommandTransaction;
+ };
+
+
+ class GetTOTP : Command<CommandID::GET_CODE> {
+ //user auth
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+ uint64_t challenge; //@unused
+ uint64_t last_totp_time; //@unused
+ uint8_t last_interval; //@unused
+ uint8_t temporary_user_password[25];
+
+ bool isValid() const { return !(slot_number & 0xF0); }
+ std::string dissect() const {
+ std::stringstream ss;
+ hexdump_to_ss(temporary_user_password);
+ ss << "slot_number:\t" << (int)(slot_number) << std::endl;
+ ss << "challenge:\t" << (challenge) << std::endl;
+ ss << "last_totp_time:\t" << (last_totp_time) << std::endl;
+ ss << "last_interval:\t" << (int)(last_interval) << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ struct ResponsePayload {
+ union {
+ uint8_t whole_response[18]; //14 bytes reserved for config, but used only 1
+ struct {
+ uint32_t code;
+ union{
+ uint8_t _slot_config;
+ struct{
+ bool use_8_digits : 1;
+ bool use_enter : 1;
+ bool use_tokenID : 1;
+ };
+ };
+ } __packed ;
+ } __packed ;
+
+ bool isValid() const { return true; }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "code:\t" << (code) << std::endl;
+ ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl;
+ ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl;
+ ss << "\tuse_enter(1):\t" << use_enter << std::endl;
+ ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct ResponsePayload>
+ CommandTransaction;
+ };
+
+
+ class WriteGeneralConfig : Command<CommandID::WRITE_CONFIG> {
+ //admin auth
+ public:
+ struct CommandPayload {
+ union{
+ uint8_t config[5];
+ struct{
+ uint8_t numlock; /** 0-1: HOTP slot number from which the code will be get on double press, other value - function disabled */
+ uint8_t capslock; /** same as numlock */
+ uint8_t scrolllock; /** same as numlock */
+ uint8_t enable_user_password;
+ uint8_t delete_user_password;
+ };
+ };
+ uint8_t temporary_admin_password[25];
+
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "numlock:\t" << (int)numlock << std::endl;
+ ss << "capslock:\t" << (int)capslock << std::endl;
+ ss << "scrolllock:\t" << (int)scrolllock << std::endl;
+ ss << "enable_user_password:\t" << (bool) enable_user_password << std::endl;
+ ss << "delete_user_password:\t" << (bool) delete_user_password << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+ };
+ }
+ }
+}
+#pragma pack (pop)
+
+#endif //LIBNITROKEY_STICK10_COMMANDS_0_8_H
diff --git a/include/stick20_commands.h b/include/stick20_commands.h
new file mode 100644
index 0000000..eb58af7
--- /dev/null
+++ b/include/stick20_commands.h
@@ -0,0 +1,351 @@
+#ifndef STICK20_COMMANDS_H
+#define STICK20_COMMANDS_H
+
+
+
+#include <cstdint>
+#include "command.h"
+#include <string>
+#include <sstream>
+#include "device_proto.h"
+
+#pragma pack (push,1)
+
+namespace nitrokey {
+ namespace proto {
+
+/*
+* STICK20 protocol command ids
+* a superset (almost) of STICK10
+*/
+
+ namespace stick20 {
+
+ class ChangeAdminUserPin20Current :
+ public PasswordCommand<CommandID::SEND_PASSWORD, PasswordKind::Admin> {};
+ class ChangeAdminUserPin20New :
+ public PasswordCommand<CommandID::SEND_NEW_PASSWORD, PasswordKind::Admin> {};
+ class UnlockUserPin :
+ public PasswordCommand<CommandID::UNLOCK_USER_PASSWORD, PasswordKind::Admin> {};
+
+ class EnableEncryptedPartition : public PasswordCommand<CommandID::ENABLE_CRYPTED_PARI> {};
+ class EnableHiddenEncryptedPartition : public PasswordCommand<CommandID::ENABLE_HIDDEN_CRYPTED_PARI> {};
+
+ //FIXME the volume disabling commands do not need password
+ class DisableEncryptedPartition : public PasswordCommand<CommandID::DISABLE_CRYPTED_PARI> {};
+ class DisableHiddenEncryptedPartition : public PasswordCommand<CommandID::DISABLE_HIDDEN_CRYPTED_PARI> {};
+
+ class EnableFirmwareUpdate : public PasswordCommand<CommandID::ENABLE_FIRMWARE_UPDATE> {};
+
+ class ChangeUpdatePassword : Command<CommandID::CHANGE_UPDATE_PIN> {
+ public:
+ struct CommandPayload {
+ uint8_t __gap;
+ uint8_t current_update_password[20];
+ uint8_t __gap2;
+ uint8_t new_update_password[20];
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss_volatile( current_update_password );
+ print_to_ss_volatile( new_update_password );
+ return ss.str();
+ }
+ };
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+ };
+
+ class ExportFirmware : public PasswordCommand<CommandID::EXPORT_FIRMWARE_TO_FILE> {};
+
+ class CreateNewKeys :
+ public PasswordCommand<CommandID::GENERATE_NEW_KEYS, PasswordKind::AdminPrefixed, 30> {};
+
+
+ class FillSDCardWithRandomChars : Command<CommandID::FILL_SD_CARD_WITH_RANDOM_CHARS> {
+ public:
+ enum class ChosenVolumes : uint8_t {
+ all_volumes = 0,
+ encrypted_volume = 1
+ };
+
+ struct CommandPayload {
+ uint8_t volume_flag;
+ uint8_t kind;
+ uint8_t admin_pin[20];
+
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss( (int) volume_flag );
+ print_to_ss( kind );
+ print_to_ss_volatile(admin_pin);
+ return ss.str();
+ }
+ void set_kind_user() {
+ kind = (uint8_t) 'P';
+ }
+ void set_defaults(){
+ set_kind_user();
+ volume_flag = static_cast<uint8_t>(ChosenVolumes::encrypted_volume);
+ }
+
+ } __packed;
+
+ typedef Transaction<Command<CommandID::FILL_SD_CARD_WITH_RANDOM_CHARS>::command_id(),
+ struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+ };
+
+ namespace StorageCommandResponsePayload{
+ using namespace DeviceResponseConstants;
+ static constexpr auto padding_size =
+ storage_data_absolute_address - header_size;
+ struct TransmissionData{
+ uint8_t _padding[padding_size];
+
+ uint8_t SendCounter_u8;
+ uint8_t SendDataType_u8;
+ uint8_t FollowBytesFlag_u8;
+ uint8_t SendSize_u8;
+
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "_padding:" << std::endl
+ << ::nitrokey::misc::hexdump((const uint8_t *) (_padding),
+ sizeof _padding);
+ print_to_ss((int) SendCounter_u8);
+ print_to_ss((int) SendDataType_u8);
+ print_to_ss((int) FollowBytesFlag_u8);
+ print_to_ss((int) SendSize_u8);
+ return ss.str();
+ }
+
+ } __packed;
+ }
+
+ namespace DeviceConfigurationResponsePacket{
+
+ struct ResponsePayload {
+ StorageCommandResponsePayload::TransmissionData transmission_data;
+
+ uint16_t MagicNumber_StickConfig_u16;
+ /**
+ * READ_WRITE_ACTIVE = ReadWriteFlagUncryptedVolume_u8 == 0;
+ */
+ uint8_t ReadWriteFlagUncryptedVolume_u8;
+ uint8_t ReadWriteFlagCryptedVolume_u8;
+
+ union{
+ uint8_t VersionInfo_au8[4];
+ struct {
+ uint8_t _reserved;
+ uint8_t minor;
+ uint8_t _reserved2;
+ uint8_t major;
+ } __packed versionInfo;
+ } __packed;
+
+ uint8_t ReadWriteFlagHiddenVolume_u8;
+ uint8_t FirmwareLocked_u8;
+
+ union{
+ uint8_t NewSDCardFound_u8;
+ struct {
+ bool NewCard :1;
+ uint8_t Counter :7;
+ } __packed NewSDCardFound_st;
+ } __packed;
+
+ /**
+ * SD card FILLED with random chars
+ */
+ uint8_t SDFillWithRandomChars_u8;
+ uint32_t ActiveSD_CardID_u32;
+ union{
+ uint8_t VolumeActiceFlag_u8;
+ struct {
+ bool unencrypted :1;
+ bool encrypted :1;
+ bool hidden :1;
+ } __packed VolumeActiceFlag_st;
+ } __packed;
+ uint8_t NewSmartCardFound_u8;
+ uint8_t UserPwRetryCount;
+ uint8_t AdminPwRetryCount;
+ uint32_t ActiveSmartCardID_u32;
+ uint8_t StickKeysNotInitiated;
+
+ bool isValid() const { return true; }
+
+ std::string dissect() const {
+ std::stringstream ss;
+
+ print_to_ss(transmission_data.dissect());
+ print_to_ss( MagicNumber_StickConfig_u16 );
+ print_to_ss((int) ReadWriteFlagUncryptedVolume_u8 );
+ print_to_ss((int) ReadWriteFlagCryptedVolume_u8 );
+ print_to_ss((int) ReadWriteFlagHiddenVolume_u8 );
+ print_to_ss((int) VersionInfo_au8[1] );
+ print_to_ss((int) VersionInfo_au8[3] );
+ print_to_ss((int) FirmwareLocked_u8 );
+ print_to_ss((int) NewSDCardFound_u8 );
+ print_to_ss((int) NewSDCardFound_st.NewCard );
+ print_to_ss((int) NewSDCardFound_st.Counter );
+ print_to_ss((int) SDFillWithRandomChars_u8 );
+ print_to_ss( ActiveSD_CardID_u32 );
+ print_to_ss((int) VolumeActiceFlag_u8 );
+ print_to_ss((int) VolumeActiceFlag_st.unencrypted );
+ print_to_ss((int) VolumeActiceFlag_st.encrypted );
+ print_to_ss((int) VolumeActiceFlag_st.hidden);
+ print_to_ss((int) NewSmartCardFound_u8 );
+ print_to_ss((int) UserPwRetryCount );
+ print_to_ss((int) AdminPwRetryCount );
+ print_to_ss( ActiveSmartCardID_u32 );
+ print_to_ss((int) StickKeysNotInitiated );
+
+ return ss.str();
+ }
+ } __packed;
+ }
+
+ class SendStartup : Command<CommandID::SEND_STARTUP> {
+ public:
+ struct CommandPayload {
+ uint64_t localtime; // POSIX seconds from epoch start, supports until year 2106
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss( localtime );
+ return ss.str();
+ }
+ void set_defaults(){
+ localtime =
+ std::chrono::duration_cast<std::chrono::seconds> (
+ std::chrono::system_clock::now().time_since_epoch()).count();
+ }
+ }__packed;
+
+ using ResponsePayload = DeviceConfigurationResponsePacket::ResponsePayload;
+
+ typedef Transaction<command_id(), struct CommandPayload, ResponsePayload>
+ CommandTransaction;
+ };
+
+
+// TODO fix original nomenclature
+ class SendSetReadonlyToUncryptedVolume : public PasswordCommand<CommandID::ENABLE_READONLY_UNCRYPTED_LUN> {};
+ class SendSetReadwriteToUncryptedVolume : public PasswordCommand<CommandID::ENABLE_READWRITE_UNCRYPTED_LUN> {};
+ class SendClearNewSdCardFound : public PasswordCommand<CommandID::CLEAR_NEW_SD_CARD_FOUND> {};
+
+ class GetDeviceStatus : Command<CommandID::GET_DEVICE_STATUS> {
+ public:
+ using ResponsePayload = DeviceConfigurationResponsePacket::ResponsePayload;
+
+ typedef Transaction<command_id(), struct EmptyPayload, ResponsePayload>
+ CommandTransaction;
+ };
+
+ class GetSDCardOccupancy : Command<CommandID::SD_CARD_HIGH_WATERMARK> {
+ public:
+ struct ResponsePayload {
+ uint8_t WriteLevelMin;
+ uint8_t WriteLevelMax;
+ uint8_t ReadLevelMin;
+ uint8_t ReadLevelMax;
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss((int) WriteLevelMin);
+ print_to_ss((int) WriteLevelMax);
+ print_to_ss((int) ReadLevelMin);
+ print_to_ss((int) ReadLevelMax);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct EmptyPayload, struct ResponsePayload>
+ CommandTransaction;
+ };
+
+
+ class SetupHiddenVolume : Command<CommandID::SEND_HIDDEN_VOLUME_SETUP> {
+ public:
+ constexpr static int MAX_HIDDEN_VOLUME_PASSWORD_SIZE = 20;
+ struct CommandPayload {
+ uint8_t SlotNr_u8;
+ uint8_t StartBlockPercent_u8;
+ uint8_t EndBlockPercent_u8;
+ uint8_t HiddenVolumePassword_au8[MAX_HIDDEN_VOLUME_PASSWORD_SIZE];
+ std::string dissect() const {
+ std::stringstream ss;
+ print_to_ss((int) SlotNr_u8);
+ print_to_ss((int) StartBlockPercent_u8);
+ print_to_ss((int) EndBlockPercent_u8);
+ print_to_ss_volatile(HiddenVolumePassword_au8);
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+ };
+
+
+//disable this command for now
+// class LockFirmware : public PasswordCommand<CommandID::SEND_LOCK_STICK_HARDWARE> {};
+
+ class ProductionTest : Command<CommandID::PRODUCTION_TEST> {
+ public:
+ struct ResponsePayload {
+
+ StorageCommandResponsePayload::TransmissionData transmission_data;
+
+ uint8_t FirmwareVersion_au8[2]; // 2 byte // 2
+ uint8_t FirmwareVersionInternal_u8; // 1 byte // 3
+ uint8_t SD_Card_Size_u8; // 1 byte // 4
+ uint32_t CPU_CardID_u32; // 4 byte // 8
+ uint32_t SmartCardID_u32; // 4 byte // 12
+ uint32_t SD_CardID_u32; // 4 byte // 16
+ uint8_t SC_UserPwRetryCount; // User PIN retry count 1 byte // 17
+ uint8_t SC_AdminPwRetryCount; // Admin PIN retry count 1 byte // 18
+ uint8_t SD_Card_ManufacturingYear_u8; // 1 byte // 19
+ uint8_t SD_Card_ManufacturingMonth_u8; // 1 byte // 20
+ uint16_t SD_Card_OEM_u16; // 2 byte // 22
+ uint16_t SD_WriteSpeed_u16; // in kbyte / sec 2 byte // 24
+ uint8_t SD_Card_Manufacturer_u8; // 1 byte // 25
+
+ bool isValid() const { return true; }
+
+ std::string dissect() const {
+ std::stringstream ss;
+
+ print_to_ss(transmission_data.dissect());
+ print_to_ss((int) FirmwareVersion_au8[0]);
+ print_to_ss((int) FirmwareVersion_au8[1]);
+ print_to_ss((int) FirmwareVersionInternal_u8);
+ print_to_ss((int) SD_Card_Size_u8);
+ print_to_ss( CPU_CardID_u32);
+ print_to_ss( SmartCardID_u32);
+ print_to_ss( SD_CardID_u32);
+ print_to_ss((int) SC_UserPwRetryCount);
+ print_to_ss((int) SC_AdminPwRetryCount);
+ print_to_ss((int) SD_Card_ManufacturingYear_u8);
+ print_to_ss((int) SD_Card_ManufacturingMonth_u8);
+ print_to_ss( SD_Card_OEM_u16);
+ print_to_ss( SD_WriteSpeed_u16);
+ print_to_ss((int) SD_Card_Manufacturer_u8);
+ return ss.str();
+ }
+
+ } __packed;
+
+ typedef Transaction<command_id(), struct EmptyPayload, struct ResponsePayload>
+ CommandTransaction;
+ };
+
+ }
+ }
+}
+
+#undef print_to_ss
+#pragma pack (pop)
+
+#endif
diff --git a/libnitrokey.pc.in b/libnitrokey.pc.in
new file mode 100644
index 0000000..17cde1d
--- /dev/null
+++ b/libnitrokey.pc.in
@@ -0,0 +1,10 @@
+libdir=@CMAKE_INSTALL_FULL_LIBDIR@
+includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
+
+Name: libnitrokey
+Description: Library for communicating with Nitrokey in a clean and easy manner
+Version: @libnitrokey_VERSION@
+Requires.private: hidapi-libusb
+
+Libs: -L${libdir} -lnitrokey
+Cflags: -I${includedir}
diff --git a/libnitrokey.pro b/libnitrokey.pro
new file mode 100644
index 0000000..f7711bf
--- /dev/null
+++ b/libnitrokey.pro
@@ -0,0 +1,74 @@
+# Created by and for Qt Creator. This file was created for editing the project sources only.
+# You may attempt to use it for building too, by modifying this file here.
+
+CONFIG += c++14 shared debug
+
+TEMPLATE = lib
+TARGET = nitrokey
+
+HEADERS = \
+ $$PWD/hidapi/hidapi/hidapi.h \
+ $$PWD/include/command.h \
+ $$PWD/include/command_id.h \
+ $$PWD/include/CommandFailedException.h \
+ $$PWD/include/cxx_semantics.h \
+ $$PWD/include/device.h \
+ $$PWD/include/device_proto.h \
+ $$PWD/include/DeviceCommunicationExceptions.h \
+ $$PWD/include/dissect.h \
+ $$PWD/include/inttypes.h \
+ $$PWD/include/LibraryException.h \
+ $$PWD/include/log.h \
+ $$PWD/include/LongOperationInProgressException.h \
+ $$PWD/include/misc.h \
+ $$PWD/include/NitrokeyManager.h \
+ $$PWD/include/stick10_commands.h \
+ $$PWD/include/stick10_commands_0.8.h \
+ $$PWD/include/stick20_commands.h \
+ $$PWD/NK_C_API.h
+
+SOURCES = \
+ $$PWD/command_id.cc \
+ $$PWD/device.cc \
+ $$PWD/DeviceCommunicationExceptions.cpp \
+ $$PWD/log.cc \
+ $$PWD/misc.cc \
+ $$PWD/NitrokeyManager.cc \
+ $$PWD/NK_C_API.cc
+
+
+tests {
+ SOURCES += \
+ $$PWD/unittest/catch_main.cpp \
+ $$PWD/unittest/test.cc \
+ $$PWD/unittest/test2.cc \
+ $$PWD/unittest/test3.cc \
+ $$PWD/unittest/test_C_API.cpp \
+ $$PWD/unittest/test_HOTP.cc
+}
+
+unix:!macx{
+# SOURCES += $$PWD/hidapi/linux/hid.c
+ LIBS += -lhidapi-libusb
+}
+
+macx{
+ SOURCES += $$PWD/hidapi/mac/hid.c
+ LIBS+= -framework IOKit -framework CoreFoundation
+}
+
+win32 {
+ SOURCES += $$PWD/hidapi/windows/hid.c
+ LIBS += -lsetupapi
+}
+
+INCLUDEPATH = \
+ $$PWD/. \
+ $$PWD/hidapi/hidapi \
+ $$PWD/include \
+ $$PWD/include/hidapi \
+ $$PWD/unittest \
+ $$PWD/unittest/Catch/single_include
+
+#DEFINES =
+
diff --git a/log.cc b/log.cc
new file mode 100644
index 0000000..4a929aa
--- /dev/null
+++ b/log.cc
@@ -0,0 +1,71 @@
+#include <iostream>
+#include <string>
+#include <ctime>
+#include <iomanip>
+#include "log.h"
+
+#include <sstream>
+
+namespace nitrokey {
+ namespace log {
+
+ Log *Log::mp_instance = nullptr;
+ StdlogHandler stdlog_handler;
+
+ std::string LogHandler::loglevel_to_str(Loglevel lvl) {
+ switch (lvl) {
+ case Loglevel::DEBUG_L1:
+ return std::string("DEBUG_L1");
+ case Loglevel::DEBUG_L2:
+ return std::string("DEBUG_L2");
+ case Loglevel::DEBUG:
+ return std::string("DEBUG");
+ case Loglevel::INFO:
+ return std::string("INFO");
+ case Loglevel::WARNING:
+ return std::string("WARNING");
+ case Loglevel::ERROR:
+ return std::string("ERROR");
+ }
+ return std::string("");
+ }
+
+ void Log::operator()(const std::string &logstr, Loglevel lvl) {
+ if (mp_loghandler != nullptr)
+ if ((int) lvl <= (int) m_loglevel) mp_loghandler->print(logstr, lvl);
+ }
+
+ void StdlogHandler::print(const std::string &str, Loglevel lvl) {
+ std::string s = format_message_to_string(str, lvl);
+ std::clog << s;
+ }
+
+ void FunctionalLogHandler::print(const std::string &str, Loglevel lvl) {
+ std::string s = format_message_to_string(str, lvl);
+ log_function(s);
+ }
+
+ std::string LogHandler::format_message_to_string(const std::string &str, const Loglevel &lvl) {
+ static bool last_short = false;
+ if (str.length() == 1){
+ last_short = true;
+ return str;
+ }
+ time_t t = time(nullptr);
+ tm tm = *localtime(&t);
+
+ std::stringstream s;
+ s
+ << (last_short? "\n" : "")
+ << "[" << std::put_time(&tm, "%c") << "]"
+ << "[" << loglevel_to_str(lvl) << "]\t"
+ << str << std::endl;
+ last_short = false;
+ return s.str();
+ }
+
+ FunctionalLogHandler::FunctionalLogHandler(log_function_type _log_function) {
+ log_function = _log_function;
+ }
+ }
+}
diff --git a/misc.cc b/misc.cc
new file mode 100644
index 0000000..d7278da
--- /dev/null
+++ b/misc.cc
@@ -0,0 +1,101 @@
+#include <sstream>
+#include <string>
+#include "misc.h"
+#include "inttypes.h"
+#include <cstdlib>
+#include <cstring>
+#include "LibraryException.h"
+#include <vector>
+
+namespace nitrokey {
+namespace misc {
+
+
+
+::std::vector<uint8_t> hex_string_to_byte(const char* hexString){
+ const size_t big_string_size = 256; //arbitrary 'big' number
+ const size_t s_size = strlen(hexString);
+ const size_t d_size = s_size/2;
+ if (s_size%2!=0 || s_size>big_string_size){
+ throw InvalidHexString(0);
+ }
+ auto data = ::std::vector<uint8_t>();
+ data.reserve(d_size);
+
+ char buf[2];
+ for(size_t i=0; i<s_size; i++){
+
+ char c = hexString[i];
+ bool char_from_range = (('0' <= c && c <='9') || ('A' <= c && c <= 'F') || ('a' <= c && c<= 'f'));
+ if (!char_from_range){
+ throw InvalidHexString(c);
+ }
+ buf[i%2] = c;
+ if (i%2==1){
+ data.push_back( strtoul(buf, NULL, 16) & 0xFF );
+ }
+ }
+ return data;
+};
+
+#include <cctype>
+::std::string hexdump(const uint8_t *p, size_t size, bool print_header,
+ bool print_ascii, bool print_empty) {
+ ::std::stringstream out;
+ char formatbuf[128];
+ const uint8_t *pstart = p;
+
+ for (const uint8_t *pend = p + size; p < pend;) {
+ if (print_header){
+ snprintf(formatbuf, 128, "%04x\t", static_cast<int> (p - pstart));
+ out << formatbuf;
+ }
+
+ const uint8_t* pp = p;
+ for (const uint8_t *le = p + 16; p < le; p++) {
+ if (p < pend){
+ snprintf(formatbuf, 128, "%02x ", uint8_t(*p));
+ out << formatbuf;
+ } else {
+ if(print_empty)
+ out << "-- ";
+ }
+
+ }
+ if(print_ascii){
+ out << " ";
+ for (const uint8_t *le = pp + 16; pp < le && pp < pend; pp++) {
+ if (std::isgraph(*pp))
+ out << uint8_t(*pp);
+ else
+ out << '.';
+ }
+ }
+ out << ::std::endl;
+ }
+ return out.str();
+}
+
+static uint32_t _crc32(uint32_t crc, uint32_t data) {
+ int i;
+ crc = crc ^ data;
+
+ for (i = 0; i < 32; i++) {
+ if (crc & 0x80000000)
+ crc = (crc << 1) ^ 0x04C11DB7; // polynomial used in STM32
+ else
+ crc = (crc << 1);
+ }
+
+ return crc;
+}
+
+uint32_t stm_crc32(const uint8_t *data, size_t size) {
+ uint32_t crc = 0xffffffff;
+ const uint32_t *pend = (const uint32_t *)(data + size);
+ for (const uint32_t *p = (const uint32_t *)(data); p < pend; p++)
+ crc = _crc32(crc, *p);
+ return crc;
+}
+}
+}
diff --git a/python_bindings_example.py b/python_bindings_example.py
new file mode 100755
index 0000000..37b70b8
--- /dev/null
+++ b/python_bindings_example.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+import cffi
+from enum import Enum
+
+"""
+This example will print 10 HOTP codes from just written HOTP#2 slot.
+For more examples of use please refer to unittest/test_*.py files.
+"""
+
+ffi = cffi.FFI()
+get_string = ffi.string
+
+class DeviceErrorCode(Enum):
+ STATUS_OK = 0
+ NOT_PROGRAMMED = 3
+ WRONG_PASSWORD = 4
+ STATUS_NOT_AUTHORIZED = 5
+ STATUS_AES_DEC_FAILED = 0xa
+
+
+def get_library():
+ fp = 'NK_C_API.h' # path to C API header
+
+ declarations = []
+ with open(fp, 'r') as f:
+ declarations = f.readlines()
+
+ a = iter(declarations)
+ for declaration in a:
+ if declaration.startswith('NK_C_API'):
+ declaration = declaration.replace('NK_C_API', '').strip()
+ while not ';' in declaration:
+ declaration += (next(a)).strip()
+ #print(declaration)
+ ffi.cdef(declaration, override=True)
+
+ C = None
+ import os, sys
+ path_build = os.path.join(".", "build")
+ paths = [ os.path.join(path_build,"libnitrokey-log.so"),
+ os.path.join(path_build,"libnitrokey.so")]
+ for p in paths:
+ print p
+ if os.path.exists(p):
+ C = ffi.dlopen(p)
+ break
+ else:
+ print("File does not exist: " + p)
+ print("Trying another")
+ if not C:
+ print("No library file found")
+ sys.exit(1)
+
+ return C
+
+
+def get_hotp_code(lib, i):
+ return lib.NK_get_hotp_code(i)
+
+def to_hex(ss):
+ return ''.join([ format(ord(s),'02x') for s in ss ])
+
+print('Warning!')
+print('This example will change your configuration on inserted stick and overwrite your HOTP#2 slot.')
+print('Please write "continue" to continue or any other string to quit')
+a = raw_input()
+
+if not a == 'continue':
+ exit()
+
+ADMIN = raw_input('Please enter your admin PIN (empty string uses 12345678)')
+ADMIN = ADMIN or '12345678' # use default if empty string
+
+show_log = raw_input('Should log messages be shown (please write "yes" to enable)?') == 'yes'
+libnitrokey = get_library()
+libnitrokey.NK_set_debug(show_log) # do not show debug messages
+
+ADMIN_TEMP = '123123123'
+RFC_SECRET = to_hex('12345678901234567890')
+
+# libnitrokey.NK_login('S') # connect only to Nitrokey Storage device
+# libnitrokey.NK_login('P') # connect only to Nitrokey Pro device
+device_connected = libnitrokey.NK_login_auto() # connect to any Nitrokey Stick
+if device_connected:
+ print('Connected to Nitrokey device!')
+else:
+ print('Could not connect to Nitrokey device!')
+ exit()
+use_8_digits = True
+pin_correct = libnitrokey.NK_first_authenticate(ADMIN, ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+if pin_correct:
+ print('Your PIN is correct!')
+else:
+ print('Your PIN is not correct! Please try again. Please be careful to not lock your stick!')
+ retry_count_left = libnitrokey.NK_get_admin_retry_count()
+ print('Retry count left: %d' % retry_count_left )
+ exit()
+
+# For function parameters documentation please check NK_C_API.h
+assert libnitrokey.NK_write_config(255, 255, 255, False, True, ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+libnitrokey.NK_first_authenticate(ADMIN, ADMIN_TEMP)
+libnitrokey.NK_write_hotp_slot(1, 'python_test', RFC_SECRET, 0, use_8_digits, False, False, "",
+ ADMIN_TEMP)
+# RFC test according to: https://tools.ietf.org/html/rfc4226#page-32
+test_data = [
+ 1284755224, 1094287082, 137359152, 1726969429, 1640338314, 868254676, 1918287922, 82162583, 673399871,
+ 645520489,
+]
+print('Getting HOTP code from Nitrokey Pro (RFC test, 8 digits): ')
+for i in range(10):
+ hotp_slot_1_code = get_hotp_code(libnitrokey, 1)
+ print('%d: %d, should be %s' % (i, hotp_slot_1_code, str(test_data[i])[-8:] ))
+libnitrokey.NK_logout() # disconnect device
diff --git a/.gitattributes b/unittest/Catch/.gitattributes
index a2d66d3..a2d66d3 100644
--- a/.gitattributes
+++ b/unittest/Catch/.gitattributes
diff --git a/unittest/Catch/.gitignore b/unittest/Catch/.gitignore
new file mode 100644
index 0000000..3ca4b5c
--- /dev/null
+++ b/unittest/Catch/.gitignore
@@ -0,0 +1,22 @@
+*.build
+*.pbxuser
+*.mode1v3
+*.ncb
+*.suo
+Debug
+Release
+*.user
+*.xcuserstate
+.DS_Store
+xcuserdata
+CatchSelfTest.xcscheme
+Breakpoints.xcbkptlist
+projects/VS2010/TestCatch/_UpgradeReport_Files/
+projects/VS2010/TestCatch/TestCatch/TestCatch.vcxproj.filters
+projects/VisualStudio/TestCatch/UpgradeLog.XML
+UpgradeLog.XML
+Resources/DWARF
+projects/XCode/iOSTest/Build
+*.pyc
+DerivedData
+*.xccheckout
diff --git a/unittest/Catch/.travis.yml b/unittest/Catch/.travis.yml
new file mode 100644
index 0000000..e63a76a
--- /dev/null
+++ b/unittest/Catch/.travis.yml
@@ -0,0 +1,163 @@
+language: cpp
+sudo: false
+
+cache:
+ ccache: true
+ directories:
+ - $HOME/.ccache
+
+env:
+ global:
+ - USE_CCACHE=1
+ - CCACHE_COMPRESS=1
+ - CCACHE_MAXSIZE=200M
+ - CCACHE_CPP2=1
+
+
+matrix:
+ include:
+
+ # 1/ Linux Clang Builds
+ - os: linux
+ compiler: clang
+ addons: &clang35
+ apt:
+ sources: ['llvm-toolchain-precise-3.5', 'ubuntu-toolchain-r-test']
+ packages: ['clang-3.5']
+ env: COMPILER='ccache clang++-3.5' BUILD_TYPE='Release'
+
+ - os: linux
+ compiler: clang
+ addons: *clang35
+ env: COMPILER='ccache clang++-3.5' BUILD_TYPE='Debug'
+
+
+ - os: linux
+ compiler: clang
+ addons: &clang36
+ apt:
+ sources: ['llvm-toolchain-precise-3.6', 'ubuntu-toolchain-r-test']
+ packages: ['clang-3.6']
+ env: COMPILER='ccache clang++-3.6' BUILD_TYPE='Release'
+
+ - os: linux
+ compiler: clang
+ addons: *clang36
+ env: COMPILER='ccache clang++-3.6' BUILD_TYPE='Debug'
+
+
+ - os: linux
+ compiler: clang
+ addons: &clang37
+ apt:
+ sources: ['llvm-toolchain-precise-3.7', 'ubuntu-toolchain-r-test']
+ packages: ['clang-3.7']
+ env: COMPILER='ccache clang++-3.7' BUILD_TYPE='Release'
+
+ - os: linux
+ compiler: clang
+ addons: *clang37
+ env: COMPILER='ccache clang++-3.7' BUILD_TYPE='Debug'
+
+
+ - os: linux
+ compiler: clang
+ addons: &clang38
+ apt:
+ sources: ['llvm-toolchain-precise', 'ubuntu-toolchain-r-test']
+ packages: ['clang-3.8']
+ env: COMPILER='ccache clang++-3.8' BUILD_TYPE='Release'
+
+ - os: linux
+ compiler: clang
+ addons: *clang38
+ env: COMPILER='ccache clang++-3.8' BUILD_TYPE='Debug'
+
+
+ # 2/ Linux GCC Builds
+ - os: linux
+ compiler: gcc
+ addons: &gcc48
+ apt:
+ sources: ['ubuntu-toolchain-r-test']
+ packages: ['g++-4.8']
+ env: COMPILER='ccache g++-4.8' BUILD_TYPE='Release'
+
+ - os: linux
+ compiler: gcc
+ addons: *gcc48
+ env: COMPILER='ccache g++-4.8' BUILD_TYPE='Debug'
+
+
+ - os: linux
+ compiler: gcc
+ addons: &gcc49
+ apt:
+ sources: ['ubuntu-toolchain-r-test']
+ packages: ['g++-4.9']
+ env: COMPILER='ccache g++-4.9' BUILD_TYPE='Release'
+
+ - os: linux
+ compiler: gcc
+ addons: *gcc49
+ env: COMPILER='ccache g++-4.9' BUILD_TYPE='Debug'
+
+
+ - os: linux
+ compiler: gcc
+ addons: &gcc5
+ apt:
+ sources: ['ubuntu-toolchain-r-test']
+ packages: ['g++-5']
+ env: COMPILER='ccache g++-5' BUILD_TYPE='Release'
+
+ - os: linux
+ compiler: gcc
+ addons: *gcc5
+ env: COMPILER='ccache g++-5' BUILD_TYPE='Debug'
+
+
+ # 3/ OSX Clang Builds
+ - os: osx
+ osx_image: xcode6.4
+ compiler: clang
+ env: COMPILER='ccache clang++' BUILD_TYPE='Debug'
+
+ - os: osx
+ osx_image: xcode6.4
+ compiler: clang
+ env: COMPILER='ccache clang++' BUILD_TYPE='Release'
+
+
+ - os: osx
+ osx_image: xcode7
+ compiler: clang
+ env: COMPILER='ccache clang++' BUILD_TYPE='Debug'
+
+ - os: osx
+ osx_image: xcode7
+ compiler: clang
+ env: COMPILER='ccache clang++' BUILD_TYPE='Release'
+
+
+install:
+ - DEPS_DIR="${TRAVIS_BUILD_DIR}/deps"
+ - mkdir -p ${DEPS_DIR} && cd ${DEPS_DIR}
+ - |
+ if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then
+ CMAKE_URL="http://www.cmake.org/files/v3.3/cmake-3.3.2-Linux-x86_64.tar.gz"
+ mkdir cmake && travis_retry wget --quiet -O - ${CMAKE_URL} | tar --strip-components=1 -xz -C cmake
+ export PATH=${DEPS_DIR}/cmake/bin:${PATH}
+ elif [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
+ brew install cmake ccache
+ fi
+
+before_script:
+ - export CXX=${COMPILER}
+ - cd ${TRAVIS_BUILD_DIR}
+ - cmake -Hprojects/CMake -BBuild -DCMAKE_BUILD_TYPE=${BUILD_TYPE}
+ - cd Build
+
+script:
+ - make -j 2
+ - ctest -V -j 2
diff --git a/LICENSE_1_0.txt b/unittest/Catch/LICENSE_1_0.txt
index 36b7cd9..36b7cd9 100644
--- a/LICENSE_1_0.txt
+++ b/unittest/Catch/LICENSE_1_0.txt
diff --git a/unittest/Catch/README.md b/unittest/Catch/README.md
new file mode 100644
index 0000000..942053f
--- /dev/null
+++ b/unittest/Catch/README.md
@@ -0,0 +1,22 @@
+![catch logo](catch-logo-small.png)
+
+*v1.3.5*
+
+Build status (on Travis CI) [![Build Status](https://travis-ci.org/philsquared/Catch.png)](https://travis-ci.org/philsquared/Catch)
+
+<a href="https://raw.githubusercontent.com/philsquared/Catch/master/single_include/catch.hpp">The latest, single header, version can be downloaded directly using this link</a>
+
+## What's the Catch?
+
+Catch stands for C++ Automated Test Cases in Headers and is a multi-paradigm automated test framework for C++ and Objective-C (and, maybe, C). It is implemented entirely in a set of header files, but is packaged up as a single header for extra convenience.
+
+## How to use it
+This documentation comprises these three parts:
+
+* [Why do we need yet another C++ Test Framework?](docs/why-catch.md)
+* [Tutorial](docs/tutorial.md) - getting started
+* [Reference section](docs/Readme.md) - all the details
+
+## More
+* Issues and bugs can be raised on the [Issue tracker on GitHub](https://github.com/philsquared/Catch/issues)
+* For discussion or questions please use [the dedicated Google Groups forum](https://groups.google.com/forum/?fromgroups#!forum/catch-forum)
diff --git a/catch-logo-small.png b/unittest/Catch/catch-logo-small.png
index a4e74e8..a4e74e8 100644
--- a/catch-logo-small.png
+++ b/unittest/Catch/catch-logo-small.png
Binary files differ
diff --git a/docs/Readme.md b/unittest/Catch/docs/Readme.md
index 6621fb8..6621fb8 100644
--- a/docs/Readme.md
+++ b/unittest/Catch/docs/Readme.md
diff --git a/docs/assertions.md b/unittest/Catch/docs/assertions.md
index 82bb96a..82bb96a 100644
--- a/docs/assertions.md
+++ b/unittest/Catch/docs/assertions.md
diff --git a/docs/build-systems.md b/unittest/Catch/docs/build-systems.md
index b001dc1..b001dc1 100644
--- a/docs/build-systems.md
+++ b/unittest/Catch/docs/build-systems.md
diff --git a/docs/command-line.md b/unittest/Catch/docs/command-line.md
index d1d4bd4..d1d4bd4 100644
--- a/docs/command-line.md
+++ b/unittest/Catch/docs/command-line.md
diff --git a/docs/configuration.md b/unittest/Catch/docs/configuration.md
index 3305219..3305219 100644
--- a/docs/configuration.md
+++ b/unittest/Catch/docs/configuration.md
diff --git a/docs/contributing.md b/unittest/Catch/docs/contributing.md
index 49a663c..49a663c 100644
--- a/docs/contributing.md
+++ b/unittest/Catch/docs/contributing.md
diff --git a/docs/logging.md b/unittest/Catch/docs/logging.md
index a659b3e..a659b3e 100644
--- a/docs/logging.md
+++ b/unittest/Catch/docs/logging.md
diff --git a/docs/own-main.md b/unittest/Catch/docs/own-main.md
index f8c836e..f8c836e 100644
--- a/docs/own-main.md
+++ b/unittest/Catch/docs/own-main.md
diff --git a/docs/slow-compiles.md b/unittest/Catch/docs/slow-compiles.md
index 5291aec..5291aec 100644
--- a/docs/slow-compiles.md
+++ b/unittest/Catch/docs/slow-compiles.md
diff --git a/docs/test-cases-and-sections.md b/unittest/Catch/docs/test-cases-and-sections.md
index 86644f0..86644f0 100644
--- a/docs/test-cases-and-sections.md
+++ b/unittest/Catch/docs/test-cases-and-sections.md
diff --git a/docs/test-fixtures.md b/unittest/Catch/docs/test-fixtures.md
index 6bef762..6bef762 100644
--- a/docs/test-fixtures.md
+++ b/unittest/Catch/docs/test-fixtures.md
diff --git a/docs/tostring.md b/unittest/Catch/docs/tostring.md
index dbb6cb8..dbb6cb8 100644
--- a/docs/tostring.md
+++ b/unittest/Catch/docs/tostring.md
diff --git a/docs/tutorial.md b/unittest/Catch/docs/tutorial.md
index 0fdaff9..0fdaff9 100644
--- a/docs/tutorial.md
+++ b/unittest/Catch/docs/tutorial.md
diff --git a/docs/why-catch.md b/unittest/Catch/docs/why-catch.md
index 93488d2..93488d2 100644
--- a/docs/why-catch.md
+++ b/unittest/Catch/docs/why-catch.md
diff --git a/include/catch.hpp b/unittest/Catch/include/catch.hpp
index 332fcd0..332fcd0 100644
--- a/include/catch.hpp
+++ b/unittest/Catch/include/catch.hpp
diff --git a/include/catch_session.hpp b/unittest/Catch/include/catch_session.hpp
index 3884b95..3884b95 100644
--- a/include/catch_session.hpp
+++ b/unittest/Catch/include/catch_session.hpp
diff --git a/include/catch_with_main.hpp b/unittest/Catch/include/catch_with_main.hpp
index 54aa651..54aa651 100644
--- a/include/catch_with_main.hpp
+++ b/unittest/Catch/include/catch_with_main.hpp
diff --git a/include/external/clara.h b/unittest/Catch/include/external/clara.h
index f2ea14b..f2ea14b 100644
--- a/include/external/clara.h
+++ b/unittest/Catch/include/external/clara.h
diff --git a/include/external/tbc_text_format.h b/unittest/Catch/include/external/tbc_text_format.h
index a63d6a1..a63d6a1 100644
--- a/include/external/tbc_text_format.h
+++ b/unittest/Catch/include/external/tbc_text_format.h
diff --git a/include/internal/catch_approx.hpp b/unittest/Catch/include/internal/catch_approx.hpp
index f5dba61..f5dba61 100644
--- a/include/internal/catch_approx.hpp
+++ b/unittest/Catch/include/internal/catch_approx.hpp
diff --git a/include/internal/catch_assertionresult.h b/unittest/Catch/include/internal/catch_assertionresult.h
index 99b3a7c..99b3a7c 100644
--- a/include/internal/catch_assertionresult.h
+++ b/unittest/Catch/include/internal/catch_assertionresult.h
diff --git a/include/internal/catch_assertionresult.hpp b/unittest/Catch/include/internal/catch_assertionresult.hpp
index bd59de9..bd59de9 100644
--- a/include/internal/catch_assertionresult.hpp
+++ b/unittest/Catch/include/internal/catch_assertionresult.hpp
diff --git a/include/internal/catch_capture.hpp b/unittest/Catch/include/internal/catch_capture.hpp
index 1496fe3..1496fe3 100644
--- a/include/internal/catch_capture.hpp
+++ b/unittest/Catch/include/internal/catch_capture.hpp
diff --git a/include/internal/catch_clara.h b/unittest/Catch/include/internal/catch_clara.h
index bfe2f4b..bfe2f4b 100644
--- a/include/internal/catch_clara.h
+++ b/unittest/Catch/include/internal/catch_clara.h
diff --git a/include/internal/catch_commandline.hpp b/unittest/Catch/include/internal/catch_commandline.hpp
index 6926559..6926559 100644
--- a/include/internal/catch_commandline.hpp
+++ b/unittest/Catch/include/internal/catch_commandline.hpp
diff --git a/include/internal/catch_common.h b/unittest/Catch/include/internal/catch_common.h
index 7f9cfc4..7f9cfc4 100644
--- a/include/internal/catch_common.h
+++ b/unittest/Catch/include/internal/catch_common.h
diff --git a/include/internal/catch_common.hpp b/unittest/Catch/include/internal/catch_common.hpp
index 2342ae6..2342ae6 100644
--- a/include/internal/catch_common.hpp
+++ b/unittest/Catch/include/internal/catch_common.hpp
diff --git a/include/internal/catch_compiler_capabilities.h b/unittest/Catch/include/internal/catch_compiler_capabilities.h
index cd34537..cd34537 100644
--- a/include/internal/catch_compiler_capabilities.h
+++ b/unittest/Catch/include/internal/catch_compiler_capabilities.h
diff --git a/include/internal/catch_config.hpp b/unittest/Catch/include/internal/catch_config.hpp
index 2665f47..2665f47 100644
--- a/include/internal/catch_config.hpp
+++ b/unittest/Catch/include/internal/catch_config.hpp
diff --git a/include/internal/catch_console_colour.hpp b/unittest/Catch/include/internal/catch_console_colour.hpp
index f0a8a69..f0a8a69 100644
--- a/include/internal/catch_console_colour.hpp
+++ b/unittest/Catch/include/internal/catch_console_colour.hpp
diff --git a/include/internal/catch_console_colour_impl.hpp b/unittest/Catch/include/internal/catch_console_colour_impl.hpp
index f776952..f776952 100644
--- a/include/internal/catch_console_colour_impl.hpp
+++ b/unittest/Catch/include/internal/catch_console_colour_impl.hpp
diff --git a/include/internal/catch_context.h b/unittest/Catch/include/internal/catch_context.h
index 9304b30..9304b30 100644
--- a/include/internal/catch_context.h
+++ b/unittest/Catch/include/internal/catch_context.h
diff --git a/include/internal/catch_context_impl.hpp b/unittest/Catch/include/internal/catch_context_impl.hpp
index 030f29e..030f29e 100644
--- a/include/internal/catch_context_impl.hpp
+++ b/unittest/Catch/include/internal/catch_context_impl.hpp
diff --git a/include/internal/catch_debugger.h b/unittest/Catch/include/internal/catch_debugger.h
index 0dd36aa..0dd36aa 100644
--- a/include/internal/catch_debugger.h
+++ b/unittest/Catch/include/internal/catch_debugger.h
diff --git a/include/internal/catch_debugger.hpp b/unittest/Catch/include/internal/catch_debugger.hpp
index 8c55266..8c55266 100644
--- a/include/internal/catch_debugger.hpp
+++ b/unittest/Catch/include/internal/catch_debugger.hpp
diff --git a/include/internal/catch_default_main.hpp b/unittest/Catch/include/internal/catch_default_main.hpp
index 54202fb..54202fb 100644
--- a/include/internal/catch_default_main.hpp
+++ b/unittest/Catch/include/internal/catch_default_main.hpp
diff --git a/include/internal/catch_evaluate.hpp b/unittest/Catch/include/internal/catch_evaluate.hpp
index b2f47cd..b2f47cd 100644
--- a/include/internal/catch_evaluate.hpp
+++ b/unittest/Catch/include/internal/catch_evaluate.hpp
diff --git a/include/internal/catch_exception_translator_registry.hpp b/unittest/Catch/include/internal/catch_exception_translator_registry.hpp
index c4bdb40..c4bdb40 100644
--- a/include/internal/catch_exception_translator_registry.hpp
+++ b/unittest/Catch/include/internal/catch_exception_translator_registry.hpp
diff --git a/include/internal/catch_expression_lhs.hpp b/unittest/Catch/include/internal/catch_expression_lhs.hpp
index 51b803e..51b803e 100644
--- a/include/internal/catch_expression_lhs.hpp
+++ b/unittest/Catch/include/internal/catch_expression_lhs.hpp
diff --git a/include/internal/catch_fatal_condition.hpp b/unittest/Catch/include/internal/catch_fatal_condition.hpp
index dd21d59..dd21d59 100644
--- a/include/internal/catch_fatal_condition.hpp
+++ b/unittest/Catch/include/internal/catch_fatal_condition.hpp
diff --git a/include/internal/catch_generators.hpp b/unittest/Catch/include/internal/catch_generators.hpp
index 84eb22f..84eb22f 100644
--- a/include/internal/catch_generators.hpp
+++ b/unittest/Catch/include/internal/catch_generators.hpp
diff --git a/include/internal/catch_generators_impl.hpp b/unittest/Catch/include/internal/catch_generators_impl.hpp
index fea699a..fea699a 100644
--- a/include/internal/catch_generators_impl.hpp
+++ b/unittest/Catch/include/internal/catch_generators_impl.hpp
diff --git a/include/internal/catch_impl.hpp b/unittest/Catch/include/internal/catch_impl.hpp
index 53c8957..53c8957 100644
--- a/include/internal/catch_impl.hpp
+++ b/unittest/Catch/include/internal/catch_impl.hpp
diff --git a/include/internal/catch_interfaces_capture.h b/unittest/Catch/include/internal/catch_interfaces_capture.h
index b7b6e32..b7b6e32 100644
--- a/include/internal/catch_interfaces_capture.h
+++ b/unittest/Catch/include/internal/catch_interfaces_capture.h
diff --git a/include/internal/catch_interfaces_config.h b/unittest/Catch/include/internal/catch_interfaces_config.h
index 17914b4..17914b4 100644
--- a/include/internal/catch_interfaces_config.h
+++ b/unittest/Catch/include/internal/catch_interfaces_config.h
diff --git a/include/internal/catch_interfaces_exception.h b/unittest/Catch/include/internal/catch_interfaces_exception.h
index 186995a..186995a 100644
--- a/include/internal/catch_interfaces_exception.h
+++ b/unittest/Catch/include/internal/catch_interfaces_exception.h
diff --git a/include/internal/catch_interfaces_generators.h b/unittest/Catch/include/internal/catch_interfaces_generators.h
index d163d5a..d163d5a 100644
--- a/include/internal/catch_interfaces_generators.h
+++ b/unittest/Catch/include/internal/catch_interfaces_generators.h
diff --git a/include/internal/catch_interfaces_registry_hub.h b/unittest/Catch/include/internal/catch_interfaces_registry_hub.h
index ec06ca2..ec06ca2 100644
--- a/include/internal/catch_interfaces_registry_hub.h
+++ b/unittest/Catch/include/internal/catch_interfaces_registry_hub.h
diff --git a/include/internal/catch_interfaces_reporter.h b/unittest/Catch/include/internal/catch_interfaces_reporter.h
index d1c6e70..d1c6e70 100644
--- a/include/internal/catch_interfaces_reporter.h
+++ b/unittest/Catch/include/internal/catch_interfaces_reporter.h
diff --git a/include/internal/catch_interfaces_runner.h b/unittest/Catch/include/internal/catch_interfaces_runner.h
index 25decfb..25decfb 100644
--- a/include/internal/catch_interfaces_runner.h
+++ b/unittest/Catch/include/internal/catch_interfaces_runner.h
diff --git a/include/internal/catch_interfaces_tag_alias_registry.h b/unittest/Catch/include/internal/catch_interfaces_tag_alias_registry.h
index cd6ac51..cd6ac51 100644
--- a/include/internal/catch_interfaces_tag_alias_registry.h
+++ b/unittest/Catch/include/internal/catch_interfaces_tag_alias_registry.h
diff --git a/include/internal/catch_interfaces_testcase.h b/unittest/Catch/include/internal/catch_interfaces_testcase.h
index a1052b7..a1052b7 100644
--- a/include/internal/catch_interfaces_testcase.h
+++ b/unittest/Catch/include/internal/catch_interfaces_testcase.h
diff --git a/include/internal/catch_legacy_reporter_adapter.h b/unittest/Catch/include/internal/catch_legacy_reporter_adapter.h
index 72c43d7..72c43d7 100644
--- a/include/internal/catch_legacy_reporter_adapter.h
+++ b/unittest/Catch/include/internal/catch_legacy_reporter_adapter.h
diff --git a/include/internal/catch_legacy_reporter_adapter.hpp b/unittest/Catch/include/internal/catch_legacy_reporter_adapter.hpp
index 6034581..6034581 100644
--- a/include/internal/catch_legacy_reporter_adapter.hpp
+++ b/unittest/Catch/include/internal/catch_legacy_reporter_adapter.hpp
diff --git a/include/internal/catch_list.hpp b/unittest/Catch/include/internal/catch_list.hpp
index 4c87ccf..4c87ccf 100644
--- a/include/internal/catch_list.hpp
+++ b/unittest/Catch/include/internal/catch_list.hpp
diff --git a/include/internal/catch_matchers.hpp b/unittest/Catch/include/internal/catch_matchers.hpp
index ab8fec1..ab8fec1 100644
--- a/include/internal/catch_matchers.hpp
+++ b/unittest/Catch/include/internal/catch_matchers.hpp
diff --git a/include/internal/catch_message.h b/unittest/Catch/include/internal/catch_message.h
index 84ff95e..84ff95e 100644
--- a/include/internal/catch_message.h
+++ b/unittest/Catch/include/internal/catch_message.h
diff --git a/include/internal/catch_message.hpp b/unittest/Catch/include/internal/catch_message.hpp
index 42866be..42866be 100644
--- a/include/internal/catch_message.hpp
+++ b/unittest/Catch/include/internal/catch_message.hpp
diff --git a/include/internal/catch_notimplemented_exception.h b/unittest/Catch/include/internal/catch_notimplemented_exception.h
index 2e38b98..2e38b98 100644
--- a/include/internal/catch_notimplemented_exception.h
+++ b/unittest/Catch/include/internal/catch_notimplemented_exception.h
diff --git a/include/internal/catch_notimplemented_exception.hpp b/unittest/Catch/include/internal/catch_notimplemented_exception.hpp
index 4d263f1..4d263f1 100644
--- a/include/internal/catch_notimplemented_exception.hpp
+++ b/unittest/Catch/include/internal/catch_notimplemented_exception.hpp
diff --git a/include/internal/catch_objc.hpp b/unittest/Catch/include/internal/catch_objc.hpp
index 489cf55..489cf55 100644
--- a/include/internal/catch_objc.hpp
+++ b/unittest/Catch/include/internal/catch_objc.hpp
diff --git a/include/internal/catch_objc_arc.hpp b/unittest/Catch/include/internal/catch_objc_arc.hpp
index 6bcd6b8..6bcd6b8 100644
--- a/include/internal/catch_objc_arc.hpp
+++ b/unittest/Catch/include/internal/catch_objc_arc.hpp
diff --git a/include/internal/catch_option.hpp b/unittest/Catch/include/internal/catch_option.hpp
index 5413abf..5413abf 100644
--- a/include/internal/catch_option.hpp
+++ b/unittest/Catch/include/internal/catch_option.hpp
diff --git a/include/internal/catch_platform.h b/unittest/Catch/include/internal/catch_platform.h
index 0142dc1..0142dc1 100644
--- a/include/internal/catch_platform.h
+++ b/unittest/Catch/include/internal/catch_platform.h
diff --git a/include/internal/catch_ptr.hpp b/unittest/Catch/include/internal/catch_ptr.hpp
index 940e5d1..940e5d1 100644
--- a/include/internal/catch_ptr.hpp
+++ b/unittest/Catch/include/internal/catch_ptr.hpp
diff --git a/include/internal/catch_reenable_warnings.h b/unittest/Catch/include/internal/catch_reenable_warnings.h
index 33574e0..33574e0 100644
--- a/include/internal/catch_reenable_warnings.h
+++ b/unittest/Catch/include/internal/catch_reenable_warnings.h
diff --git a/include/internal/catch_registry_hub.hpp b/unittest/Catch/include/internal/catch_registry_hub.hpp
index 35293bf..35293bf 100644
--- a/include/internal/catch_registry_hub.hpp
+++ b/unittest/Catch/include/internal/catch_registry_hub.hpp
diff --git a/include/internal/catch_reporter_registrars.hpp b/unittest/Catch/include/internal/catch_reporter_registrars.hpp
index 7bd7b61..7bd7b61 100644
--- a/include/internal/catch_reporter_registrars.hpp
+++ b/unittest/Catch/include/internal/catch_reporter_registrars.hpp
diff --git a/include/internal/catch_reporter_registry.hpp b/unittest/Catch/include/internal/catch_reporter_registry.hpp
index 71f23ff..71f23ff 100644
--- a/include/internal/catch_reporter_registry.hpp
+++ b/unittest/Catch/include/internal/catch_reporter_registry.hpp
diff --git a/include/internal/catch_result_builder.h b/unittest/Catch/include/internal/catch_result_builder.h
index 8900266..8900266 100644
--- a/include/internal/catch_result_builder.h
+++ b/unittest/Catch/include/internal/catch_result_builder.h
diff --git a/include/internal/catch_result_builder.hpp b/unittest/Catch/include/internal/catch_result_builder.hpp
index d453fec..d453fec 100644
--- a/include/internal/catch_result_builder.hpp
+++ b/unittest/Catch/include/internal/catch_result_builder.hpp
diff --git a/include/internal/catch_result_type.h b/unittest/Catch/include/internal/catch_result_type.h
index 4c3d77d..4c3d77d 100644
--- a/include/internal/catch_result_type.h
+++ b/unittest/Catch/include/internal/catch_result_type.h
diff --git a/include/internal/catch_run_context.hpp b/unittest/Catch/include/internal/catch_run_context.hpp
index da5990a..da5990a 100644
--- a/include/internal/catch_run_context.hpp
+++ b/unittest/Catch/include/internal/catch_run_context.hpp
diff --git a/include/internal/catch_section.h b/unittest/Catch/include/internal/catch_section.h
index d8b3ae4..d8b3ae4 100644
--- a/include/internal/catch_section.h
+++ b/unittest/Catch/include/internal/catch_section.h
diff --git a/include/internal/catch_section.hpp b/unittest/Catch/include/internal/catch_section.hpp
index de65c4c..de65c4c 100644
--- a/include/internal/catch_section.hpp
+++ b/unittest/Catch/include/internal/catch_section.hpp
diff --git a/include/internal/catch_section_info.h b/unittest/Catch/include/internal/catch_section_info.h
index 00b6560..00b6560 100644
--- a/include/internal/catch_section_info.h
+++ b/unittest/Catch/include/internal/catch_section_info.h
diff --git a/include/internal/catch_section_info.hpp b/unittest/Catch/include/internal/catch_section_info.hpp
index 6258a28..6258a28 100644
--- a/include/internal/catch_section_info.hpp
+++ b/unittest/Catch/include/internal/catch_section_info.hpp
diff --git a/include/internal/catch_stream.h b/unittest/Catch/include/internal/catch_stream.h
index 5f22ad6..5f22ad6 100644
--- a/include/internal/catch_stream.h
+++ b/unittest/Catch/include/internal/catch_stream.h
diff --git a/include/internal/catch_stream.hpp b/unittest/Catch/include/internal/catch_stream.hpp
index 2703df5..2703df5 100644
--- a/include/internal/catch_stream.hpp
+++ b/unittest/Catch/include/internal/catch_stream.hpp
diff --git a/include/internal/catch_streambuf.h b/unittest/Catch/include/internal/catch_streambuf.h
index 4f5e238..4f5e238 100644
--- a/include/internal/catch_streambuf.h
+++ b/unittest/Catch/include/internal/catch_streambuf.h
diff --git a/include/internal/catch_suppress_warnings.h b/unittest/Catch/include/internal/catch_suppress_warnings.h
index 8f57b28..8f57b28 100644
--- a/include/internal/catch_suppress_warnings.h
+++ b/unittest/Catch/include/internal/catch_suppress_warnings.h
diff --git a/include/internal/catch_tag_alias.h b/unittest/Catch/include/internal/catch_tag_alias.h
index 6dde74a..6dde74a 100644
--- a/include/internal/catch_tag_alias.h
+++ b/unittest/Catch/include/internal/catch_tag_alias.h
diff --git a/include/internal/catch_tag_alias_registry.h b/unittest/Catch/include/internal/catch_tag_alias_registry.h
index 98c796e..98c796e 100644
--- a/include/internal/catch_tag_alias_registry.h
+++ b/unittest/Catch/include/internal/catch_tag_alias_registry.h
diff --git a/include/internal/catch_tag_alias_registry.hpp b/unittest/Catch/include/internal/catch_tag_alias_registry.hpp
index e5ad11b..e5ad11b 100644
--- a/include/internal/catch_tag_alias_registry.hpp
+++ b/unittest/Catch/include/internal/catch_tag_alias_registry.hpp
diff --git a/include/internal/catch_test_case_info.h b/unittest/Catch/include/internal/catch_test_case_info.h
index 6ab1f37..6ab1f37 100644
--- a/include/internal/catch_test_case_info.h
+++ b/unittest/Catch/include/internal/catch_test_case_info.h
diff --git a/include/internal/catch_test_case_info.hpp b/unittest/Catch/include/internal/catch_test_case_info.hpp
index 90f5341..90f5341 100644
--- a/include/internal/catch_test_case_info.hpp
+++ b/unittest/Catch/include/internal/catch_test_case_info.hpp
diff --git a/include/internal/catch_test_case_registry_impl.hpp b/unittest/Catch/include/internal/catch_test_case_registry_impl.hpp
index a2b041a..a2b041a 100644
--- a/include/internal/catch_test_case_registry_impl.hpp
+++ b/unittest/Catch/include/internal/catch_test_case_registry_impl.hpp
diff --git a/include/internal/catch_test_case_tracker.hpp b/unittest/Catch/include/internal/catch_test_case_tracker.hpp
index 505c3ab..505c3ab 100644
--- a/include/internal/catch_test_case_tracker.hpp
+++ b/unittest/Catch/include/internal/catch_test_case_tracker.hpp
diff --git a/include/internal/catch_test_registry.hpp b/unittest/Catch/include/internal/catch_test_registry.hpp
index bd27ba0..bd27ba0 100644
--- a/include/internal/catch_test_registry.hpp
+++ b/unittest/Catch/include/internal/catch_test_registry.hpp
diff --git a/include/internal/catch_test_spec.hpp b/unittest/Catch/include/internal/catch_test_spec.hpp
index 7e4ea9d..7e4ea9d 100644
--- a/include/internal/catch_test_spec.hpp
+++ b/unittest/Catch/include/internal/catch_test_spec.hpp
diff --git a/include/internal/catch_test_spec_parser.hpp b/unittest/Catch/include/internal/catch_test_spec_parser.hpp
index 3f794c6..3f794c6 100644
--- a/include/internal/catch_test_spec_parser.hpp
+++ b/unittest/Catch/include/internal/catch_test_spec_parser.hpp
diff --git a/include/internal/catch_text.h b/unittest/Catch/include/internal/catch_text.h
index b66751f..b66751f 100644
--- a/include/internal/catch_text.h
+++ b/unittest/Catch/include/internal/catch_text.h
diff --git a/include/internal/catch_timer.h b/unittest/Catch/include/internal/catch_timer.h
index 22e9e63..22e9e63 100644
--- a/include/internal/catch_timer.h
+++ b/unittest/Catch/include/internal/catch_timer.h
diff --git a/include/internal/catch_timer.hpp b/unittest/Catch/include/internal/catch_timer.hpp
index 2ba709e..2ba709e 100644
--- a/include/internal/catch_timer.hpp
+++ b/unittest/Catch/include/internal/catch_timer.hpp
diff --git a/include/internal/catch_tostring.h b/unittest/Catch/include/internal/catch_tostring.h
index e6f7ec9..e6f7ec9 100644
--- a/include/internal/catch_tostring.h
+++ b/unittest/Catch/include/internal/catch_tostring.h
diff --git a/include/internal/catch_tostring.hpp b/unittest/Catch/include/internal/catch_tostring.hpp
index 0a20ee2..0a20ee2 100644
--- a/include/internal/catch_tostring.hpp
+++ b/unittest/Catch/include/internal/catch_tostring.hpp
diff --git a/include/internal/catch_totals.hpp b/unittest/Catch/include/internal/catch_totals.hpp
index 551e294..551e294 100644
--- a/include/internal/catch_totals.hpp
+++ b/unittest/Catch/include/internal/catch_totals.hpp
diff --git a/include/internal/catch_version.h b/unittest/Catch/include/internal/catch_version.h
index 1a79c5c..1a79c5c 100644
--- a/include/internal/catch_version.h
+++ b/unittest/Catch/include/internal/catch_version.h
diff --git a/include/internal/catch_version.hpp b/unittest/Catch/include/internal/catch_version.hpp
index 7aa9353..7aa9353 100644
--- a/include/internal/catch_version.hpp
+++ b/unittest/Catch/include/internal/catch_version.hpp
diff --git a/include/internal/catch_wildcard_pattern.hpp b/unittest/Catch/include/internal/catch_wildcard_pattern.hpp
index cd8b07e..cd8b07e 100644
--- a/include/internal/catch_wildcard_pattern.hpp
+++ b/unittest/Catch/include/internal/catch_wildcard_pattern.hpp
diff --git a/include/internal/catch_xmlwriter.hpp b/unittest/Catch/include/internal/catch_xmlwriter.hpp
index c59725b..c59725b 100644
--- a/include/internal/catch_xmlwriter.hpp
+++ b/unittest/Catch/include/internal/catch_xmlwriter.hpp
diff --git a/include/reporters/catch_reporter_bases.hpp b/unittest/Catch/include/reporters/catch_reporter_bases.hpp
index 4fdeb19..4fdeb19 100644
--- a/include/reporters/catch_reporter_bases.hpp
+++ b/unittest/Catch/include/reporters/catch_reporter_bases.hpp
diff --git a/include/reporters/catch_reporter_compact.hpp b/unittest/Catch/include/reporters/catch_reporter_compact.hpp
index a5a1729..a5a1729 100644
--- a/include/reporters/catch_reporter_compact.hpp
+++ b/unittest/Catch/include/reporters/catch_reporter_compact.hpp
diff --git a/include/reporters/catch_reporter_console.hpp b/unittest/Catch/include/reporters/catch_reporter_console.hpp
index e736703..e736703 100644
--- a/include/reporters/catch_reporter_console.hpp
+++ b/unittest/Catch/include/reporters/catch_reporter_console.hpp
diff --git a/include/reporters/catch_reporter_junit.hpp b/unittest/Catch/include/reporters/catch_reporter_junit.hpp
index e656882..e656882 100644
--- a/include/reporters/catch_reporter_junit.hpp
+++ b/unittest/Catch/include/reporters/catch_reporter_junit.hpp
diff --git a/include/reporters/catch_reporter_multi.hpp b/unittest/Catch/include/reporters/catch_reporter_multi.hpp
index d328266..d328266 100644
--- a/include/reporters/catch_reporter_multi.hpp
+++ b/unittest/Catch/include/reporters/catch_reporter_multi.hpp
diff --git a/include/reporters/catch_reporter_teamcity.hpp b/unittest/Catch/include/reporters/catch_reporter_teamcity.hpp
index 1e633f1..1e633f1 100644
--- a/include/reporters/catch_reporter_teamcity.hpp
+++ b/unittest/Catch/include/reporters/catch_reporter_teamcity.hpp
diff --git a/include/reporters/catch_reporter_xml.hpp b/unittest/Catch/include/reporters/catch_reporter_xml.hpp
index bbe8780..bbe8780 100644
--- a/include/reporters/catch_reporter_xml.hpp
+++ b/unittest/Catch/include/reporters/catch_reporter_xml.hpp
diff --git a/projects/CMake/CMakeLists.txt b/unittest/Catch/projects/CMake/CMakeLists.txt
index 960d9fb..960d9fb 100644
--- a/projects/CMake/CMakeLists.txt
+++ b/unittest/Catch/projects/CMake/CMakeLists.txt
diff --git a/projects/SelfTest/ApproxTests.cpp b/unittest/Catch/projects/SelfTest/ApproxTests.cpp
index 5365659..5365659 100644
--- a/projects/SelfTest/ApproxTests.cpp
+++ b/unittest/Catch/projects/SelfTest/ApproxTests.cpp
diff --git a/projects/SelfTest/BDDTests.cpp b/unittest/Catch/projects/SelfTest/BDDTests.cpp
index 2c8bc24..2c8bc24 100644
--- a/projects/SelfTest/BDDTests.cpp
+++ b/unittest/Catch/projects/SelfTest/BDDTests.cpp
diff --git a/projects/SelfTest/Baselines/console.std.approved.txt b/unittest/Catch/projects/SelfTest/Baselines/console.std.approved.txt
index 29870b9..29870b9 100644
--- a/projects/SelfTest/Baselines/console.std.approved.txt
+++ b/unittest/Catch/projects/SelfTest/Baselines/console.std.approved.txt
diff --git a/projects/SelfTest/Baselines/console.sw.approved.txt b/unittest/Catch/projects/SelfTest/Baselines/console.sw.approved.txt
index cd73c2a..cd73c2a 100644
--- a/projects/SelfTest/Baselines/console.sw.approved.txt
+++ b/unittest/Catch/projects/SelfTest/Baselines/console.sw.approved.txt
diff --git a/projects/SelfTest/Baselines/console.swa4.approved.txt b/unittest/Catch/projects/SelfTest/Baselines/console.swa4.approved.txt
index 0793f1d..0793f1d 100644
--- a/projects/SelfTest/Baselines/console.swa4.approved.txt
+++ b/unittest/Catch/projects/SelfTest/Baselines/console.swa4.approved.txt
diff --git a/projects/SelfTest/Baselines/junit.sw.approved.txt b/unittest/Catch/projects/SelfTest/Baselines/junit.sw.approved.txt
index 110ee8c..110ee8c 100644
--- a/projects/SelfTest/Baselines/junit.sw.approved.txt
+++ b/unittest/Catch/projects/SelfTest/Baselines/junit.sw.approved.txt
diff --git a/projects/SelfTest/Baselines/xml.sw.approved.txt b/unittest/Catch/projects/SelfTest/Baselines/xml.sw.approved.txt
index c0af02f..c0af02f 100644
--- a/projects/SelfTest/Baselines/xml.sw.approved.txt
+++ b/unittest/Catch/projects/SelfTest/Baselines/xml.sw.approved.txt
diff --git a/projects/SelfTest/ClassTests.cpp b/unittest/Catch/projects/SelfTest/ClassTests.cpp
index a470784..a470784 100644
--- a/projects/SelfTest/ClassTests.cpp
+++ b/unittest/Catch/projects/SelfTest/ClassTests.cpp
diff --git a/projects/SelfTest/CmdLineTests.cpp b/unittest/Catch/projects/SelfTest/CmdLineTests.cpp
index 719c89b..719c89b 100644
--- a/projects/SelfTest/CmdLineTests.cpp
+++ b/unittest/Catch/projects/SelfTest/CmdLineTests.cpp
diff --git a/projects/SelfTest/ConditionTests.cpp b/unittest/Catch/projects/SelfTest/ConditionTests.cpp
index d1b7ed2..d1b7ed2 100644
--- a/projects/SelfTest/ConditionTests.cpp
+++ b/unittest/Catch/projects/SelfTest/ConditionTests.cpp
diff --git a/projects/SelfTest/EnumToString.cpp b/unittest/Catch/projects/SelfTest/EnumToString.cpp
index 6917d8a..6917d8a 100644
--- a/projects/SelfTest/EnumToString.cpp
+++ b/unittest/Catch/projects/SelfTest/EnumToString.cpp
diff --git a/projects/SelfTest/ExceptionTests.cpp b/unittest/Catch/projects/SelfTest/ExceptionTests.cpp
index 5ef91a1..5ef91a1 100644
--- a/projects/SelfTest/ExceptionTests.cpp
+++ b/unittest/Catch/projects/SelfTest/ExceptionTests.cpp
diff --git a/projects/SelfTest/GeneratorTests.cpp b/unittest/Catch/projects/SelfTest/GeneratorTests.cpp
index af08b1d..af08b1d 100644
--- a/projects/SelfTest/GeneratorTests.cpp
+++ b/unittest/Catch/projects/SelfTest/GeneratorTests.cpp
diff --git a/projects/SelfTest/MessageTests.cpp b/unittest/Catch/projects/SelfTest/MessageTests.cpp
index 87a85a8..87a85a8 100644
--- a/projects/SelfTest/MessageTests.cpp
+++ b/unittest/Catch/projects/SelfTest/MessageTests.cpp
diff --git a/projects/SelfTest/MiscTests.cpp b/unittest/Catch/projects/SelfTest/MiscTests.cpp
index cf7f48c..cf7f48c 100644
--- a/projects/SelfTest/MiscTests.cpp
+++ b/unittest/Catch/projects/SelfTest/MiscTests.cpp
diff --git a/projects/SelfTest/PartTrackerTests.cpp b/unittest/Catch/projects/SelfTest/PartTrackerTests.cpp
index 2925635..2925635 100644
--- a/projects/SelfTest/PartTrackerTests.cpp
+++ b/unittest/Catch/projects/SelfTest/PartTrackerTests.cpp
diff --git a/projects/SelfTest/SurrogateCpps/catch_common.cpp b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_common.cpp
index 72f90bd..72f90bd 100644
--- a/projects/SelfTest/SurrogateCpps/catch_common.cpp
+++ b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_common.cpp
diff --git a/projects/SelfTest/SurrogateCpps/catch_console_colour.cpp b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_console_colour.cpp
index e7378c0..e7378c0 100644
--- a/projects/SelfTest/SurrogateCpps/catch_console_colour.cpp
+++ b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_console_colour.cpp
diff --git a/projects/SelfTest/SurrogateCpps/catch_debugger.cpp b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_debugger.cpp
index 33f76ae..33f76ae 100644
--- a/projects/SelfTest/SurrogateCpps/catch_debugger.cpp
+++ b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_debugger.cpp
diff --git a/projects/SelfTest/SurrogateCpps/catch_interfaces_capture.cpp b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_capture.cpp
index 2d1c8f3..2d1c8f3 100644
--- a/projects/SelfTest/SurrogateCpps/catch_interfaces_capture.cpp
+++ b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_capture.cpp
diff --git a/projects/SelfTest/SurrogateCpps/catch_interfaces_config.cpp b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_config.cpp
index de0f18d..de0f18d 100644
--- a/projects/SelfTest/SurrogateCpps/catch_interfaces_config.cpp
+++ b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_config.cpp
diff --git a/projects/SelfTest/SurrogateCpps/catch_interfaces_exception.cpp b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_exception.cpp
index 183d87d..183d87d 100644
--- a/projects/SelfTest/SurrogateCpps/catch_interfaces_exception.cpp
+++ b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_exception.cpp
diff --git a/projects/SelfTest/SurrogateCpps/catch_interfaces_generators.cpp b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_generators.cpp
index 271b1bc..271b1bc 100644
--- a/projects/SelfTest/SurrogateCpps/catch_interfaces_generators.cpp
+++ b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_generators.cpp
diff --git a/projects/SelfTest/SurrogateCpps/catch_interfaces_registry_hub.cpp b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_registry_hub.cpp
index ffece3b..ffece3b 100644
--- a/projects/SelfTest/SurrogateCpps/catch_interfaces_registry_hub.cpp
+++ b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_registry_hub.cpp
diff --git a/projects/SelfTest/SurrogateCpps/catch_interfaces_reporter.cpp b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_reporter.cpp
index 544f9d0..544f9d0 100644
--- a/projects/SelfTest/SurrogateCpps/catch_interfaces_reporter.cpp
+++ b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_reporter.cpp
diff --git a/projects/SelfTest/SurrogateCpps/catch_interfaces_runner.cpp b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_runner.cpp
index 401de26..401de26 100644
--- a/projects/SelfTest/SurrogateCpps/catch_interfaces_runner.cpp
+++ b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_runner.cpp
diff --git a/projects/SelfTest/SurrogateCpps/catch_interfaces_testcase.cpp b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_testcase.cpp
index 733dcb4..733dcb4 100644
--- a/projects/SelfTest/SurrogateCpps/catch_interfaces_testcase.cpp
+++ b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_interfaces_testcase.cpp
diff --git a/projects/SelfTest/SurrogateCpps/catch_message.cpp b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_message.cpp
index ad2f5e7..ad2f5e7 100644
--- a/projects/SelfTest/SurrogateCpps/catch_message.cpp
+++ b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_message.cpp
diff --git a/projects/SelfTest/SurrogateCpps/catch_option.cpp b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_option.cpp
index 35e3282..35e3282 100644
--- a/projects/SelfTest/SurrogateCpps/catch_option.cpp
+++ b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_option.cpp
diff --git a/projects/SelfTest/SurrogateCpps/catch_ptr.cpp b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_ptr.cpp
index a25ad66..a25ad66 100644
--- a/projects/SelfTest/SurrogateCpps/catch_ptr.cpp
+++ b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_ptr.cpp
diff --git a/projects/SelfTest/SurrogateCpps/catch_stream.cpp b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_stream.cpp
index 7aaffa1..7aaffa1 100644
--- a/projects/SelfTest/SurrogateCpps/catch_stream.cpp
+++ b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_stream.cpp
diff --git a/projects/SelfTest/SurrogateCpps/catch_streambuf.cpp b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_streambuf.cpp
index 5c0043c..5c0043c 100644
--- a/projects/SelfTest/SurrogateCpps/catch_streambuf.cpp
+++ b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_streambuf.cpp
diff --git a/projects/SelfTest/SurrogateCpps/catch_test_spec.cpp b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_test_spec.cpp
index 46bf89f..46bf89f 100644
--- a/projects/SelfTest/SurrogateCpps/catch_test_spec.cpp
+++ b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_test_spec.cpp
diff --git a/projects/SelfTest/SurrogateCpps/catch_xmlwriter.cpp b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_xmlwriter.cpp
index 15328ef..15328ef 100644
--- a/projects/SelfTest/SurrogateCpps/catch_xmlwriter.cpp
+++ b/unittest/Catch/projects/SelfTest/SurrogateCpps/catch_xmlwriter.cpp
diff --git a/projects/SelfTest/TagAliasTests.cpp b/unittest/Catch/projects/SelfTest/TagAliasTests.cpp
index 002a93c..002a93c 100644
--- a/projects/SelfTest/TagAliasTests.cpp
+++ b/unittest/Catch/projects/SelfTest/TagAliasTests.cpp
diff --git a/projects/SelfTest/TestMain.cpp b/unittest/Catch/projects/SelfTest/TestMain.cpp
index c63f469..c63f469 100644
--- a/projects/SelfTest/TestMain.cpp
+++ b/unittest/Catch/projects/SelfTest/TestMain.cpp
diff --git a/projects/SelfTest/ToStringPair.cpp b/unittest/Catch/projects/SelfTest/ToStringPair.cpp
index 8f51070..8f51070 100644
--- a/projects/SelfTest/ToStringPair.cpp
+++ b/unittest/Catch/projects/SelfTest/ToStringPair.cpp
diff --git a/projects/SelfTest/ToStringTuple.cpp b/unittest/Catch/projects/SelfTest/ToStringTuple.cpp
index 80e5453..80e5453 100644
--- a/projects/SelfTest/ToStringTuple.cpp
+++ b/unittest/Catch/projects/SelfTest/ToStringTuple.cpp
diff --git a/projects/SelfTest/ToStringVector.cpp b/unittest/Catch/projects/SelfTest/ToStringVector.cpp
index c3a8d4e..c3a8d4e 100644
--- a/projects/SelfTest/ToStringVector.cpp
+++ b/unittest/Catch/projects/SelfTest/ToStringVector.cpp
diff --git a/projects/SelfTest/ToStringWhich.cpp b/unittest/Catch/projects/SelfTest/ToStringWhich.cpp
index 1d4aa89..1d4aa89 100644
--- a/projects/SelfTest/ToStringWhich.cpp
+++ b/unittest/Catch/projects/SelfTest/ToStringWhich.cpp
diff --git a/projects/SelfTest/TrickyTests.cpp b/unittest/Catch/projects/SelfTest/TrickyTests.cpp
index eb53e42..eb53e42 100644
--- a/projects/SelfTest/TrickyTests.cpp
+++ b/unittest/Catch/projects/SelfTest/TrickyTests.cpp
diff --git a/projects/SelfTest/VariadicMacrosTests.cpp b/unittest/Catch/projects/SelfTest/VariadicMacrosTests.cpp
index b784076..b784076 100644
--- a/projects/SelfTest/VariadicMacrosTests.cpp
+++ b/unittest/Catch/projects/SelfTest/VariadicMacrosTests.cpp
diff --git a/projects/SelfTest/makefile b/unittest/Catch/projects/SelfTest/makefile
index 0a29372..0a29372 100644
--- a/projects/SelfTest/makefile
+++ b/unittest/Catch/projects/SelfTest/makefile
diff --git a/projects/VS2008/TestCatch/TestCatch.sln b/unittest/Catch/projects/VS2008/TestCatch/TestCatch.sln
index bd85bbe..bd85bbe 100644
--- a/projects/VS2008/TestCatch/TestCatch.sln
+++ b/unittest/Catch/projects/VS2008/TestCatch/TestCatch.sln
diff --git a/projects/VS2008/TestCatch/TestCatch/TestCatch.cpp b/unittest/Catch/projects/VS2008/TestCatch/TestCatch/TestCatch.cpp
index 8dd2067..8dd2067 100644
--- a/projects/VS2008/TestCatch/TestCatch/TestCatch.cpp
+++ b/unittest/Catch/projects/VS2008/TestCatch/TestCatch/TestCatch.cpp
diff --git a/projects/VS2008/TestCatch/TestCatch/TestCatch.vcproj b/unittest/Catch/projects/VS2008/TestCatch/TestCatch/TestCatch.vcproj
index d4cb0c9..d4cb0c9 100644
--- a/projects/VS2008/TestCatch/TestCatch/TestCatch.vcproj
+++ b/unittest/Catch/projects/VS2008/TestCatch/TestCatch/TestCatch.vcproj
diff --git a/projects/VS2010/TestCatch/TestCatch.sln b/unittest/Catch/projects/VS2010/TestCatch/TestCatch.sln
index efb4502..efb4502 100644
--- a/projects/VS2010/TestCatch/TestCatch.sln
+++ b/unittest/Catch/projects/VS2010/TestCatch/TestCatch.sln
diff --git a/projects/VS2010/TestCatch/TestCatch/TestCatch.vcxproj b/unittest/Catch/projects/VS2010/TestCatch/TestCatch/TestCatch.vcxproj
index 52bcea1..52bcea1 100644
--- a/projects/VS2010/TestCatch/TestCatch/TestCatch.vcxproj
+++ b/unittest/Catch/projects/VS2010/TestCatch/TestCatch/TestCatch.vcxproj
diff --git a/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj b/unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj
index a26df53..a26df53 100644
--- a/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj
+++ b/unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.pbxproj
diff --git a/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
index 804437d..804437d 100644
--- a/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ b/unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
diff --git a/projects/XCode/CatchSelfTest/CatchSelfTest/CatchSelfTest.1 b/unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTest/CatchSelfTest.1
index 28b36e2..28b36e2 100644
--- a/projects/XCode/CatchSelfTest/CatchSelfTest/CatchSelfTest.1
+++ b/unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTest/CatchSelfTest.1
diff --git a/projects/XCode/CatchSelfTest/CatchSelfTest/catch_text.cpp b/unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTest/catch_text.cpp
index a08e67b..a08e67b 100644
--- a/projects/XCode/CatchSelfTest/CatchSelfTest/catch_text.cpp
+++ b/unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTest/catch_text.cpp
diff --git a/projects/XCode/CatchSelfTest/CatchSelfTestSingle.xcodeproj/project.pbxproj b/unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTestSingle.xcodeproj/project.pbxproj
index 1eee6eb..1eee6eb 100644
--- a/projects/XCode/CatchSelfTest/CatchSelfTestSingle.xcodeproj/project.pbxproj
+++ b/unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTestSingle.xcodeproj/project.pbxproj
diff --git a/projects/XCode/CatchSelfTest/CatchSelfTestSingle.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTestSingle.xcodeproj/project.xcworkspace/contents.xcworkspacedata
index 4fcfc0e..4fcfc0e 100644
--- a/projects/XCode/CatchSelfTest/CatchSelfTestSingle.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ b/unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTestSingle.xcodeproj/project.xcworkspace/contents.xcworkspacedata
diff --git a/projects/XCode/CatchSelfTest/CatchSelfTestSingle.xcodeproj/project.xcworkspace/xcshareddata/CatchSelfTestSingle.xccheckout b/unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTestSingle.xcodeproj/project.xcworkspace/xcshareddata/CatchSelfTestSingle.xccheckout
index cce7052..cce7052 100644
--- a/projects/XCode/CatchSelfTest/CatchSelfTestSingle.xcodeproj/project.xcworkspace/xcshareddata/CatchSelfTestSingle.xccheckout
+++ b/unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTestSingle.xcodeproj/project.xcworkspace/xcshareddata/CatchSelfTestSingle.xccheckout
diff --git a/projects/XCode/CatchSelfTest/CatchSelfTestSingle/dummy.txt b/unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTestSingle/dummy.txt
index e69de29..e69de29 100644
--- a/projects/XCode/CatchSelfTest/CatchSelfTestSingle/dummy.txt
+++ b/unittest/Catch/projects/XCode/CatchSelfTest/CatchSelfTestSingle/dummy.txt
diff --git a/projects/XCode/OCTest/OCTest.xcodeproj/project.pbxproj b/unittest/Catch/projects/XCode/OCTest/OCTest.xcodeproj/project.pbxproj
index 6777e8e..6777e8e 100644
--- a/projects/XCode/OCTest/OCTest.xcodeproj/project.pbxproj
+++ b/unittest/Catch/projects/XCode/OCTest/OCTest.xcodeproj/project.pbxproj
diff --git a/projects/XCode/OCTest/OCTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/unittest/Catch/projects/XCode/OCTest/OCTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
index 119e61c..119e61c 100644
--- a/projects/XCode/OCTest/OCTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ b/unittest/Catch/projects/XCode/OCTest/OCTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
diff --git a/projects/XCode/OCTest/OCTest.xcodeproj/project.xcworkspace/xcshareddata/OCTest.xccheckout b/unittest/Catch/projects/XCode/OCTest/OCTest.xcodeproj/project.xcworkspace/xcshareddata/OCTest.xccheckout
index 2480364..2480364 100644
--- a/projects/XCode/OCTest/OCTest.xcodeproj/project.xcworkspace/xcshareddata/OCTest.xccheckout
+++ b/unittest/Catch/projects/XCode/OCTest/OCTest.xcodeproj/project.xcworkspace/xcshareddata/OCTest.xccheckout
diff --git a/projects/XCode/OCTest/OCTest/CatchOCTestCase.h b/unittest/Catch/projects/XCode/OCTest/OCTest/CatchOCTestCase.h
index bd26239..bd26239 100644
--- a/projects/XCode/OCTest/OCTest/CatchOCTestCase.h
+++ b/unittest/Catch/projects/XCode/OCTest/OCTest/CatchOCTestCase.h
diff --git a/projects/XCode/OCTest/OCTest/CatchOCTestCase.mm b/unittest/Catch/projects/XCode/OCTest/OCTest/CatchOCTestCase.mm
index fb20287..fb20287 100644
--- a/projects/XCode/OCTest/OCTest/CatchOCTestCase.mm
+++ b/unittest/Catch/projects/XCode/OCTest/OCTest/CatchOCTestCase.mm
diff --git a/projects/XCode/OCTest/OCTest/Main.mm b/unittest/Catch/projects/XCode/OCTest/OCTest/Main.mm
index 569dc4d..569dc4d 100644
--- a/projects/XCode/OCTest/OCTest/Main.mm
+++ b/unittest/Catch/projects/XCode/OCTest/OCTest/Main.mm
diff --git a/projects/XCode/OCTest/OCTest/OCTest.1 b/unittest/Catch/projects/XCode/OCTest/OCTest/OCTest.1
index 7915d02..7915d02 100644
--- a/projects/XCode/OCTest/OCTest/OCTest.1
+++ b/unittest/Catch/projects/XCode/OCTest/OCTest/OCTest.1
diff --git a/projects/XCode/OCTest/OCTest/OCTest.mm b/unittest/Catch/projects/XCode/OCTest/OCTest/OCTest.mm
index bfb2ca2..bfb2ca2 100644
--- a/projects/XCode/OCTest/OCTest/OCTest.mm
+++ b/unittest/Catch/projects/XCode/OCTest/OCTest/OCTest.mm
diff --git a/projects/XCode/OCTest/OCTest/TestObj.h b/unittest/Catch/projects/XCode/OCTest/OCTest/TestObj.h
index 8443921..8443921 100644
--- a/projects/XCode/OCTest/OCTest/TestObj.h
+++ b/unittest/Catch/projects/XCode/OCTest/OCTest/TestObj.h
diff --git a/projects/XCode/OCTest/OCTest/TestObj.m b/unittest/Catch/projects/XCode/OCTest/OCTest/TestObj.m
index 2c7dc99..2c7dc99 100644
--- a/projects/XCode/OCTest/OCTest/TestObj.m
+++ b/unittest/Catch/projects/XCode/OCTest/OCTest/TestObj.m
diff --git a/projects/XCode/iOSTest/iOSTest.xcodeproj/project.pbxproj b/unittest/Catch/projects/XCode/iOSTest/iOSTest.xcodeproj/project.pbxproj
index e968cf6..e968cf6 100644
--- a/projects/XCode/iOSTest/iOSTest.xcodeproj/project.pbxproj
+++ b/unittest/Catch/projects/XCode/iOSTest/iOSTest.xcodeproj/project.pbxproj
diff --git a/projects/XCode/iOSTest/iOSTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/unittest/Catch/projects/XCode/iOSTest/iOSTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
index aaadc7d..aaadc7d 100644
--- a/projects/XCode/iOSTest/iOSTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ b/unittest/Catch/projects/XCode/iOSTest/iOSTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
diff --git a/projects/XCode/iOSTest/iOSTest/OCTest.mm b/unittest/Catch/projects/XCode/iOSTest/iOSTest/OCTest.mm
index d476cb4..d476cb4 100644
--- a/projects/XCode/iOSTest/iOSTest/OCTest.mm
+++ b/unittest/Catch/projects/XCode/iOSTest/iOSTest/OCTest.mm
diff --git a/projects/XCode/iOSTest/iOSTest/TestObj.h b/unittest/Catch/projects/XCode/iOSTest/iOSTest/TestObj.h
index de67250..de67250 100644
--- a/projects/XCode/iOSTest/iOSTest/TestObj.h
+++ b/unittest/Catch/projects/XCode/iOSTest/iOSTest/TestObj.h
diff --git a/projects/XCode/iOSTest/iOSTest/TestObj.m b/unittest/Catch/projects/XCode/iOSTest/iOSTest/TestObj.m
index 943f445..943f445 100644
--- a/projects/XCode/iOSTest/iOSTest/TestObj.m
+++ b/unittest/Catch/projects/XCode/iOSTest/iOSTest/TestObj.m
diff --git a/projects/XCode/iOSTest/iOSTest/en.lproj/InfoPlist.strings b/unittest/Catch/projects/XCode/iOSTest/iOSTest/en.lproj/InfoPlist.strings
index 477b28f..477b28f 100644
--- a/projects/XCode/iOSTest/iOSTest/en.lproj/InfoPlist.strings
+++ b/unittest/Catch/projects/XCode/iOSTest/iOSTest/en.lproj/InfoPlist.strings
diff --git a/projects/XCode/iOSTest/iOSTest/iOSTest-Info.plist b/unittest/Catch/projects/XCode/iOSTest/iOSTest/iOSTest-Info.plist
index f74b6db..f74b6db 100644
--- a/projects/XCode/iOSTest/iOSTest/iOSTest-Info.plist
+++ b/unittest/Catch/projects/XCode/iOSTest/iOSTest/iOSTest-Info.plist
diff --git a/projects/XCode/iOSTest/iOSTest/iOSTest-Prefix.pch b/unittest/Catch/projects/XCode/iOSTest/iOSTest/iOSTest-Prefix.pch
index 403e8e4..403e8e4 100644
--- a/projects/XCode/iOSTest/iOSTest/iOSTest-Prefix.pch
+++ b/unittest/Catch/projects/XCode/iOSTest/iOSTest/iOSTest-Prefix.pch
diff --git a/projects/runners/iTchRunner/internal/iTchRunnerAppDelegate.h b/unittest/Catch/projects/runners/iTchRunner/internal/iTchRunnerAppDelegate.h
index 522291a..522291a 100644
--- a/projects/runners/iTchRunner/internal/iTchRunnerAppDelegate.h
+++ b/unittest/Catch/projects/runners/iTchRunner/internal/iTchRunnerAppDelegate.h
diff --git a/projects/runners/iTchRunner/internal/iTchRunnerMainView.h b/unittest/Catch/projects/runners/iTchRunner/internal/iTchRunnerMainView.h
index c6a6394..c6a6394 100644
--- a/projects/runners/iTchRunner/internal/iTchRunnerMainView.h
+++ b/unittest/Catch/projects/runners/iTchRunner/internal/iTchRunnerMainView.h
diff --git a/projects/runners/iTchRunner/internal/iTchRunnerReporter.h b/unittest/Catch/projects/runners/iTchRunner/internal/iTchRunnerReporter.h
index d262886..d262886 100644
--- a/projects/runners/iTchRunner/internal/iTchRunnerReporter.h
+++ b/unittest/Catch/projects/runners/iTchRunner/internal/iTchRunnerReporter.h
diff --git a/projects/runners/iTchRunner/itChRunnerMain.mm b/unittest/Catch/projects/runners/iTchRunner/itChRunnerMain.mm
index cac5fae..cac5fae 100644
--- a/projects/runners/iTchRunner/itChRunnerMain.mm
+++ b/unittest/Catch/projects/runners/iTchRunner/itChRunnerMain.mm
diff --git a/projects/runners/iTchRunner/readme b/unittest/Catch/projects/runners/iTchRunner/readme
index 2b25491..2b25491 100644
--- a/projects/runners/iTchRunner/readme
+++ b/unittest/Catch/projects/runners/iTchRunner/readme
diff --git a/scripts/approvalTests.py b/unittest/Catch/scripts/approvalTests.py
index 618812f..618812f 100644
--- a/scripts/approvalTests.py
+++ b/unittest/Catch/scripts/approvalTests.py
diff --git a/scripts/approve.py b/unittest/Catch/scripts/approve.py
index f4b66aa..f4b66aa 100644
--- a/scripts/approve.py
+++ b/unittest/Catch/scripts/approve.py
diff --git a/scripts/developBuild.py b/unittest/Catch/scripts/developBuild.py
index c244871..c244871 100644
--- a/scripts/developBuild.py
+++ b/unittest/Catch/scripts/developBuild.py
diff --git a/scripts/fixTrailingWhitespace.py b/unittest/Catch/scripts/fixTrailingWhitespace.py
index a1b6bbe..a1b6bbe 100644
--- a/scripts/fixTrailingWhitespace.py
+++ b/unittest/Catch/scripts/fixTrailingWhitespace.py
diff --git a/scripts/generateSingleHeader.py b/unittest/Catch/scripts/generateSingleHeader.py
index 419633f..419633f 100644
--- a/scripts/generateSingleHeader.py
+++ b/unittest/Catch/scripts/generateSingleHeader.py
diff --git a/scripts/majorRelease.py b/unittest/Catch/scripts/majorRelease.py
index 03b7e78..03b7e78 100644
--- a/scripts/majorRelease.py
+++ b/unittest/Catch/scripts/majorRelease.py
diff --git a/scripts/minorRelease.py b/unittest/Catch/scripts/minorRelease.py
index bbd97ed..bbd97ed 100644
--- a/scripts/minorRelease.py
+++ b/unittest/Catch/scripts/minorRelease.py
diff --git a/scripts/patchRelease.py b/unittest/Catch/scripts/patchRelease.py
index 6abf87a..6abf87a 100644
--- a/scripts/patchRelease.py
+++ b/unittest/Catch/scripts/patchRelease.py
diff --git a/scripts/releaseCommon.py b/unittest/Catch/scripts/releaseCommon.py
index 14eb235..14eb235 100644
--- a/scripts/releaseCommon.py
+++ b/unittest/Catch/scripts/releaseCommon.py
diff --git a/scripts/releaseNotes.py b/unittest/Catch/scripts/releaseNotes.py
index e083ec9..e083ec9 100644
--- a/scripts/releaseNotes.py
+++ b/unittest/Catch/scripts/releaseNotes.py
diff --git a/scripts/scriptCommon.py b/unittest/Catch/scripts/scriptCommon.py
index 6ac381a..6ac381a 100644
--- a/scripts/scriptCommon.py
+++ b/unittest/Catch/scripts/scriptCommon.py
diff --git a/single_include/catch.hpp b/unittest/Catch/single_include/catch.hpp
index 5b9bfc2..5b9bfc2 100644
--- a/single_include/catch.hpp
+++ b/unittest/Catch/single_include/catch.hpp
diff --git a/unittest/build/libnitrokey.so b/unittest/build/libnitrokey.so
new file mode 120000
index 0000000..69a5e22
--- /dev/null
+++ b/unittest/build/libnitrokey.so
@@ -0,0 +1 @@
+../../build/libnitrokey.so \ No newline at end of file
diff --git a/unittest/build/run.sh b/unittest/build/run.sh
new file mode 100755
index 0000000..2bcc580
--- /dev/null
+++ b/unittest/build/run.sh
@@ -0,0 +1 @@
+LD_LIBRARY_PATH=. ./test_HOTP
diff --git a/unittest/catch_main.cpp b/unittest/catch_main.cpp
new file mode 100644
index 0000000..c8270db
--- /dev/null
+++ b/unittest/catch_main.cpp
@@ -0,0 +1,2 @@
+#define CATCH_CONFIG_MAIN // This tells Catch to provide a main()
+#include "catch.hpp" \ No newline at end of file
diff --git a/unittest/conftest.py b/unittest/conftest.py
new file mode 100644
index 0000000..a53b005
--- /dev/null
+++ b/unittest/conftest.py
@@ -0,0 +1,83 @@
+import pytest
+
+from misc import ffi
+
+device_type = None
+
+def skip_if_device_version_lower_than(allowed_devices):
+ global device_type
+ model, version = device_type
+ infinite_version_number = 999
+ if allowed_devices.get(model, infinite_version_number) > version:
+ pytest.skip('This device model is not applicable to run this test')
+
+
+@pytest.fixture(scope="module")
+def C(request):
+ fp = '../NK_C_API.h'
+
+ declarations = []
+ with open(fp, 'r') as f:
+ declarations = f.readlines()
+
+ cnt = 0
+ a = iter(declarations)
+ for declaration in a:
+ if declaration.strip().startswith('NK_C_API'):
+ declaration = declaration.replace('NK_C_API', '').strip()
+ while ';' not in declaration:
+ declaration += (next(a)).strip()
+ # print(declaration)
+ ffi.cdef(declaration, override=True)
+ cnt +=1
+ print('Imported {} declarations'.format(cnt))
+
+ C = None
+ import os, sys
+ path_build = os.path.join("..", "build")
+ paths = [
+ os.environ.get('LIBNK_PATH', None),
+ os.path.join(path_build,"libnitrokey.so"),
+ os.path.join(path_build,"libnitrokey.dylib"),
+ os.path.join(path_build,"libnitrokey.dll"),
+ os.path.join(path_build,"nitrokey.dll"),
+ ]
+ for p in paths:
+ if not p: continue
+ print("Trying " +p)
+ p = os.path.abspath(p)
+ if os.path.exists(p):
+ print("Found: "+p)
+ C = ffi.dlopen(p)
+ break
+ else:
+ print("File does not exist: " + p)
+ if not C:
+ print("No library file found")
+ sys.exit(1)
+
+ C.NK_set_debug(False)
+ 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_major_firmware_version()
+ model = 'P' if firmware_version in [7,8] else 'S'
+ device_type = (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
+
+ # C.NK_status()
+
+ def fin():
+ print('\nFinishing connection to device')
+ C.NK_logout()
+ print('Finished')
+
+ request.addfinalizer(fin)
+ # C.NK_set_debug(True)
+ C.NK_set_debug_level(3)
+
+ return C
diff --git a/unittest/constants.py b/unittest/constants.py
new file mode 100644
index 0000000..f3e876a
--- /dev/null
+++ b/unittest/constants.py
@@ -0,0 +1,37 @@
+from misc import to_hex
+
+def bb(x):
+ return bytes(x, encoding='ascii')
+
+
+RFC_SECRET_HR = '12345678901234567890'
+RFC_SECRET = to_hex(RFC_SECRET_HR) # '31323334353637383930...'
+bbRFC_SECRET = bb(RFC_SECRET)
+
+
+# print( repr((RFC_SECRET, RFC_SECRET_, len(RFC_SECRET))) )
+
+class DefaultPasswords:
+ ADMIN = b'12345678'
+ USER = b'123456'
+ ADMIN_TEMP = b'123123123'
+ USER_TEMP = b'234234234'
+ UPDATE = b'12345678'
+ UPDATE_TEMP = b'123update123'
+
+
+class DeviceErrorCode:
+ 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
+
+
+class LibraryErrors:
+ TOO_LONG_STRING = 200
+ INVALID_SLOT = 201
+ INVALID_HEX_STRING = 202
+ TARGET_BUFFER_SIZE_SMALLER_THAN_SOURCE = 203
+
diff --git a/unittest/misc.py b/unittest/misc.py
new file mode 100644
index 0000000..f4d7731
--- /dev/null
+++ b/unittest/misc.py
@@ -0,0 +1,48 @@
+import cffi
+
+ffi = cffi.FFI()
+gs = ffi.string
+
+
+def to_hex(s):
+ return "".join("{:02x}".format(ord(c)) for c in s)
+
+
+def wait(t):
+ import time
+ msg = 'Waiting for %d seconds' % t
+ print(msg.center(40, '='))
+ time.sleep(t)
+
+
+def cast_pointer_to_tuple(obj, typen, len):
+ # usage:
+ # config = cast_pointer_to_tuple(config_raw_data, 'uint8_t', 5)
+ return tuple(ffi.cast("%s [%d]" % (typen, len), obj)[0:len])
+
+
+def get_devices_firmware_version(C):
+ firmware = C.NK_get_major_firmware_version()
+ return firmware
+
+
+def is_pro_rtm_07(C):
+ firmware = get_devices_firmware_version(C)
+ return firmware == 7
+
+
+def is_pro_rtm_08(C):
+ firmware = get_devices_firmware_version(C)
+ return firmware == 8
+
+
+def is_storage(C):
+ """
+ exact firmware storage is sent by other function
+ """
+ # TODO identify connected device directly
+ return not is_pro_rtm_08(C) and not is_pro_rtm_07(C)
+
+
+def is_long_OTP_secret_handled(C):
+ return is_pro_rtm_08(C) or is_storage(C) and get_devices_firmware_version(C) > 43
diff --git a/unittest/requirements.txt b/unittest/requirements.txt
new file mode 100644
index 0000000..5c0110b
--- /dev/null
+++ b/unittest/requirements.txt
@@ -0,0 +1,4 @@
+cffi
+pytest-repeat
+pytest-randomly
+oath \ No newline at end of file
diff --git a/unittest/setup_python_dependencies.sh b/unittest/setup_python_dependencies.sh
new file mode 100644
index 0000000..0f1a0f7
--- /dev/null
+++ b/unittest/setup_python_dependencies.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+pip install -r requirements.txt --user
diff --git a/unittest/test.cc b/unittest/test.cc
new file mode 100644
index 0000000..15235bd
--- /dev/null
+++ b/unittest/test.cc
@@ -0,0 +1,83 @@
+#include "catch.hpp"
+
+#include <iostream>
+#include <string.h>
+#include "device_proto.h"
+#include "log.h"
+#include "stick10_commands.h"
+
+using namespace std;
+using namespace nitrokey::device;
+using namespace nitrokey::proto::stick10;
+using namespace nitrokey::log;
+using namespace nitrokey::misc;
+
+using Dev10 = std::shared_ptr<Stick10>;
+
+std::string getSlotName(Dev10 stick, int slotNo) {
+ auto slot_req = get_payload<ReadSlot>();
+ slot_req.slot_number = slotNo;
+ auto slot = ReadSlot::CommandTransaction::run(stick, slot_req);
+ std::string sName(reinterpret_cast<char *>(slot.data().slot_name));
+ return sName;
+}
+
+TEST_CASE("Slot names are correct", "[slotNames]") {
+ auto stick = make_shared<Stick10>();
+ bool connected = stick->connect();
+ REQUIRE(connected == true);
+
+ Log::instance().set_loglevel(Loglevel::DEBUG);
+
+ auto resp = GetStatus::CommandTransaction::run(stick);
+
+ auto authreq = get_payload<FirstAuthenticate>();
+ strcpy((char *)(authreq.card_password), "12345678");
+ FirstAuthenticate::CommandTransaction::run(stick, authreq);
+
+ {
+ auto authreq = get_payload<EnablePasswordSafe>();
+ strcpy((char *)(authreq.user_password), "123456");
+ EnablePasswordSafe::CommandTransaction::run(stick, authreq);
+ }
+
+ //assuming these values were set earlier, thus failing on normal use
+ REQUIRE(getSlotName(stick, 0x20) == std::string("1"));
+ REQUIRE(getSlotName(stick, 0x21) == std::string("slot2"));
+
+ {
+ auto resp = GetPasswordRetryCount::CommandTransaction::run(stick);
+ REQUIRE(resp.data().password_retry_count == 3);
+ }
+ {
+ auto resp = GetUserPasswordRetryCount::CommandTransaction::run(stick);
+ REQUIRE(resp.data().password_retry_count == 3);
+ }
+
+ {
+ auto slot = get_payload<GetPasswordSafeSlotName>();
+ slot.slot_number = 0;
+ auto resp2 = GetPasswordSafeSlotName::CommandTransaction::run(stick, slot);
+ std::string sName(reinterpret_cast<char *>(resp2.data().slot_name));
+ REQUIRE(sName == std::string("web1"));
+ }
+
+ {
+ auto slot = get_payload<GetPasswordSafeSlotPassword>();
+ slot.slot_number = 0;
+ auto resp2 =
+ GetPasswordSafeSlotPassword::CommandTransaction::run(stick, slot);
+ std::string sName(reinterpret_cast<char *>(resp2.data().slot_password));
+ REQUIRE(sName == std::string("pass1"));
+ }
+
+ {
+ auto slot = get_payload<GetPasswordSafeSlotLogin>();
+ slot.slot_number = 0;
+ auto resp2 = GetPasswordSafeSlotLogin::CommandTransaction::run(stick, slot);
+ std::string sName(reinterpret_cast<char *>(resp2.data().slot_login));
+ REQUIRE(sName == std::string("login1"));
+ }
+
+ stick->disconnect();
+}
diff --git a/unittest/test2.cc b/unittest/test2.cc
new file mode 100644
index 0000000..bbe5b07
--- /dev/null
+++ b/unittest/test2.cc
@@ -0,0 +1,212 @@
+
+static const char *const default_admin_pin = "12345678";
+static const char *const default_user_pin = "123456";
+
+#include "catch.hpp"
+
+#include <iostream>
+#include <string.h>
+#include <NitrokeyManager.h>
+#include "device_proto.h"
+#include "log.h"
+//#include "stick10_commands.h"
+#include "stick20_commands.h"
+
+using namespace std;
+using namespace nitrokey::device;
+using namespace nitrokey::proto;
+using namespace nitrokey::proto::stick20;
+using namespace nitrokey::log;
+using namespace nitrokey::misc;
+
+#include <memory>
+
+template<typename CMDTYPE>
+void execute_password_command(std::shared_ptr<Device> stick, const char *password, const char kind = 'P') {
+ auto p = get_payload<CMDTYPE>();
+ if (kind == 'P'){
+ p.set_kind_user();
+ } else {
+ p.set_kind_admin();
+ }
+ strcpyT(p.password, password);
+ CMDTYPE::CommandTransaction::run(stick, p);
+ this_thread::sleep_for(1000ms);
+}
+
+/**
+ * fail on purpose (will result in failed test)
+ * disable from running unwillingly
+ */
+void SKIP_TEST() {
+ CAPTURE("Failing current test to SKIP it");
+ REQUIRE(false);
+}
+
+
+TEST_CASE("long operation test", "[test_long]") {
+ SKIP_TEST();
+
+ auto stick = make_shared<Stick20>();
+ bool connected = stick->connect();
+ REQUIRE(connected == true);
+ Log::instance().set_loglevel(Loglevel::DEBUG);
+ try{
+ auto p = get_payload<FillSDCardWithRandomChars>();
+ p.set_defaults();
+ strcpyT(p.admin_pin, default_admin_pin);
+ FillSDCardWithRandomChars::CommandTransaction::run(stick, p);
+ this_thread::sleep_for(1000ms);
+
+ CHECK(false);
+ }
+ catch (LongOperationInProgressException &progressException){
+ CHECK(true);
+ }
+
+
+ for (int i = 0; i < 30; ++i) {
+ try {
+ stick10::GetStatus::CommandTransaction::run(stick);
+ }
+ catch (LongOperationInProgressException &progressException){
+ CHECK((int)progressException.progress_bar_value>=0);
+ CAPTURE((int)progressException.progress_bar_value);
+ this_thread::sleep_for(2000ms);
+ }
+
+ }
+
+}
+
+
+TEST_CASE("test device internal status with various commands", "[fast]") {
+ auto stick = make_shared<Stick20>();
+ bool connected = stick->connect();
+ REQUIRE(connected == true);
+
+ Log::instance().set_loglevel(Loglevel::DEBUG);
+ auto p = get_payload<stick20::SendStartup>();
+ p.set_defaults();
+ auto device_status = stick20::SendStartup::CommandTransaction::run(stick, p);
+ REQUIRE(device_status.data().AdminPwRetryCount == 3);
+ REQUIRE(device_status.data().UserPwRetryCount == 3);
+ REQUIRE(device_status.data().ActiveSmartCardID_u32 != 0);
+
+ auto production_status = stick20::ProductionTest::CommandTransaction::run(stick);
+ REQUIRE(production_status.data().SD_Card_Size_u8 == 8);
+ REQUIRE(production_status.data().SD_CardID_u32 != 0);
+
+ auto sdcard_occupancy = stick20::GetSDCardOccupancy::CommandTransaction::run(stick);
+ REQUIRE((int) sdcard_occupancy.data().ReadLevelMin >= 0);
+ REQUIRE((int) sdcard_occupancy.data().ReadLevelMax <= 100);
+ REQUIRE((int) sdcard_occupancy.data().WriteLevelMin >= 0);
+ REQUIRE((int) sdcard_occupancy.data().WriteLevelMax <= 100);
+}
+
+TEST_CASE("setup hidden volume test", "[hidden]") {
+ auto stick = make_shared<Stick20>();
+ bool connected = stick->connect();
+ REQUIRE(connected == true);
+ Log::instance().set_loglevel(Loglevel::DEBUG);
+ stick10::LockDevice::CommandTransaction::run(stick);
+ this_thread::sleep_for(2000ms);
+
+ auto user_pin = default_user_pin;
+ execute_password_command<EnableEncryptedPartition>(stick, user_pin);
+
+ auto p = get_payload<stick20::SetupHiddenVolume>();
+ p.SlotNr_u8 = 0;
+ p.StartBlockPercent_u8 = 70;
+ p.EndBlockPercent_u8 = 90;
+ auto hidden_volume_password = "123123123";
+ strcpyT(p.HiddenVolumePassword_au8, hidden_volume_password);
+ stick20::SetupHiddenVolume::CommandTransaction::run(stick, p);
+ this_thread::sleep_for(2000ms);
+
+ execute_password_command<EnableHiddenEncryptedPartition>(stick, hidden_volume_password);
+}
+
+TEST_CASE("setup multiple hidden volumes", "[hidden]") {
+ auto stick = make_shared<Stick20>();
+ bool connected = stick->connect();
+ REQUIRE(connected == true);
+ Log::instance().set_loglevel(Loglevel::DEBUG);
+
+ auto user_pin = default_user_pin;
+ stick10::LockDevice::CommandTransaction::run(stick);
+ this_thread::sleep_for(2000ms);
+ execute_password_command<EnableEncryptedPartition>(stick, user_pin);
+
+ constexpr int volume_count = 4;
+ for (int i = 0; i < volume_count; ++i) {
+ auto p = get_payload<stick20::SetupHiddenVolume>();
+ p.SlotNr_u8 = i;
+ p.StartBlockPercent_u8 = 20 + 10*i;
+ p.EndBlockPercent_u8 = p.StartBlockPercent_u8+i+1;
+ auto hidden_volume_password = std::string("123123123")+std::to_string(i);
+ strcpyT(p.HiddenVolumePassword_au8, hidden_volume_password.c_str());
+ stick20::SetupHiddenVolume::CommandTransaction::run(stick, p);
+ this_thread::sleep_for(2000ms);
+ }
+
+
+ for (int i = 0; i < volume_count; ++i) {
+ execute_password_command<EnableEncryptedPartition>(stick, user_pin);
+ auto hidden_volume_password = std::string("123123123")+std::to_string(i);
+ execute_password_command<EnableHiddenEncryptedPartition>(stick, hidden_volume_password.c_str());
+ this_thread::sleep_for(2000ms);
+ }
+}
+
+
+//in case of a bug this could change update PIN to some unexpected value
+// - please save log with packet dump if this test will not pass
+TEST_CASE("update password change", "[dangerous]") {
+ SKIP_TEST();
+
+ auto stick = make_shared<Stick20>();
+ bool connected = stick->connect();
+ REQUIRE(connected == true);
+ Log::instance().set_loglevel(Loglevel::DEBUG);
+
+ auto pass1 = default_admin_pin;
+ auto pass2 = "12345678901234567890";
+
+ auto data = {
+ make_pair(pass1, pass2),
+ make_pair(pass2, pass1),
+ };
+ for (auto && password: data) {
+ auto p = get_payload<stick20::ChangeUpdatePassword>();
+ strcpyT(p.current_update_password, password.first);
+ strcpyT(p.new_update_password, password.second);
+ stick20::ChangeUpdatePassword::CommandTransaction::run(stick, p);
+ }
+}
+
+TEST_CASE("general test", "[test]") {
+ auto stick = make_shared<Stick20>();
+ bool connected = stick->connect();
+ REQUIRE(connected == true);
+
+ Log::instance().set_loglevel(Loglevel::DEBUG);
+
+ stick10::LockDevice::CommandTransaction::run(stick);
+// execute_password_command<EnableEncryptedPartition>(stick, "123456");
+// execute_password_command<DisableEncryptedPartition>(stick, "123456");
+// execute_password_command<DisableHiddenEncryptedPartition>(stick, "123123123");
+
+ execute_password_command<SendSetReadonlyToUncryptedVolume>(stick, default_user_pin);
+ execute_password_command<SendSetReadwriteToUncryptedVolume>(stick, default_user_pin);
+ execute_password_command<SendClearNewSdCardFound>(stick, default_admin_pin, 'A');
+ stick20::GetDeviceStatus::CommandTransaction::run(stick);
+ this_thread::sleep_for(1000ms);
+// execute_password_command<LockFirmware>(stick, "123123123"); //CAUTION
+// execute_password_command<EnableFirmwareUpdate>(stick, "123123123"); //CAUTION FIRMWARE PIN
+
+ execute_password_command<ExportFirmware>(stick, "12345678", 'A');
+// execute_password_command<FillSDCardWithRandomChars>(stick, "12345678", 'A');
+
+ stick10::LockDevice::CommandTransaction::run(stick);
+}
diff --git a/unittest/test3.cc b/unittest/test3.cc
new file mode 100644
index 0000000..b5289f7
--- /dev/null
+++ b/unittest/test3.cc
@@ -0,0 +1,219 @@
+
+static const char *const default_admin_pin = "12345678";
+static const char *const default_user_pin = "123456";
+const char * temporary_password = "123456789012345678901234";
+const char * RFC_SECRET = "12345678901234567890";
+
+#include "catch.hpp"
+
+#include <iostream>
+#include <string.h>
+#include <NitrokeyManager.h>
+#include "device_proto.h"
+#include "log.h"
+#include "stick10_commands_0.8.h"
+//#include "stick20_commands.h"
+
+using namespace std;
+using namespace nitrokey::device;
+using namespace nitrokey::proto;
+using namespace nitrokey::proto::stick10_08;
+using namespace nitrokey::log;
+using namespace nitrokey::misc;
+
+using Dev = Stick10;
+using Dev10 = std::shared_ptr<Dev>;
+
+void connect_and_setup(Dev10 stick) {
+ bool connected = stick->connect();
+ REQUIRE(connected == true);
+ Log::instance().set_loglevel(Loglevel::DEBUG);
+}
+
+void authorize(Dev10 stick) {
+ auto authreq = get_payload<FirstAuthenticate>();
+ strcpy((char *) (authreq.card_password), default_admin_pin);
+ strcpy((char *) (authreq.temporary_password), temporary_password);
+ FirstAuthenticate::CommandTransaction::run(stick, authreq);
+
+ auto user_auth = get_payload<UserAuthenticate>();
+ strcpyT(user_auth.temporary_password, temporary_password);
+ strcpyT(user_auth.card_password, default_user_pin);
+ UserAuthenticate::CommandTransaction::run(stick, user_auth);
+}
+
+TEST_CASE("write slot", "[pronew]"){
+ auto stick = make_shared<Dev>();
+
+ connect_and_setup(stick);
+ authorize(stick);
+
+ auto p2 = get_payload<SendOTPData>();
+ strcpyT(p2.temporary_admin_password, temporary_password);
+ p2.setTypeName();
+ strcpyT(p2.data, "test name aaa");
+ stick10_08::SendOTPData::CommandTransaction::run(stick, p2);
+
+ p2 = get_payload<SendOTPData>();
+ strcpyT(p2.temporary_admin_password, temporary_password);
+ strcpyT(p2.data, RFC_SECRET);
+ p2.setTypeSecret();
+ stick10_08::SendOTPData::CommandTransaction::run(stick, p2);
+
+ auto p = get_payload<WriteToOTPSlot>();
+ strcpyT(p.temporary_admin_password, temporary_password);
+ p.use_8_digits = true;
+ p.slot_number = 0 + 0x10;
+ p.slot_counter_or_interval = 0;
+ stick10_08::WriteToOTPSlot::CommandTransaction::run(stick, p);
+
+ auto pc = get_payload<WriteGeneralConfig>();
+ pc.enable_user_password = 0;
+ strcpyT(pc.temporary_admin_password, temporary_password);
+ WriteGeneralConfig::CommandTransaction::run(stick, pc);
+
+ auto p3 = get_payload<GetHOTP>();
+ p3.slot_number = 0 + 0x10;
+ GetHOTP::CommandTransaction::run(stick, p3);
+
+}
+
+
+TEST_CASE("erase slot", "[pronew]"){
+ auto stick = make_shared<Dev>();
+ connect_and_setup(stick);
+ authorize(stick);
+
+ auto p = get_payload<WriteGeneralConfig>();
+ p.enable_user_password = 0;
+ strcpyT(p.temporary_admin_password, temporary_password);
+ WriteGeneralConfig::CommandTransaction::run(stick, p);
+
+ auto p3 = get_payload<GetHOTP>();
+ p3.slot_number = 0 + 0x10;
+ GetHOTP::CommandTransaction::run(stick, p3);
+
+ auto erase_payload = get_payload<EraseSlot>();
+ erase_payload.slot_number = 0 + 0x10;
+ strcpyT(erase_payload.temporary_admin_password, temporary_password);
+ EraseSlot::CommandTransaction::run(stick, erase_payload);
+
+ auto p4 = get_payload<GetHOTP>();
+ p4.slot_number = 0 + 0x10;
+ REQUIRE_THROWS(
+ GetHOTP::CommandTransaction::run(stick, p4)
+ );
+}
+
+TEST_CASE("write general config", "[pronew]") {
+ auto stick = make_shared<Dev>();
+ connect_and_setup(stick);
+ authorize(stick);
+
+ auto p = get_payload<WriteGeneralConfig>();
+ p.enable_user_password = 1;
+ REQUIRE_THROWS(
+ WriteGeneralConfig::CommandTransaction::run(stick, p)
+ );
+ strcpyT(p.temporary_admin_password, temporary_password);
+ WriteGeneralConfig::CommandTransaction::run(stick, p);
+}
+
+TEST_CASE("authorize user HOTP", "[pronew]") {
+ auto stick = make_shared<Dev>();
+ connect_and_setup(stick);
+ authorize(stick);
+
+ {
+ auto p = get_payload<WriteGeneralConfig>();
+ p.enable_user_password = 1;
+ strcpyT(p.temporary_admin_password, temporary_password);
+ WriteGeneralConfig::CommandTransaction::run(stick, p);
+ }
+
+ auto p2 = get_payload<SendOTPData>();
+ strcpyT(p2.temporary_admin_password, temporary_password);
+ p2.setTypeName();
+ strcpyT(p2.data, "test name aaa");
+ stick10_08::SendOTPData::CommandTransaction::run(stick, p2);
+
+ p2 = get_payload<SendOTPData>();
+ strcpyT(p2.temporary_admin_password, temporary_password);
+ strcpyT(p2.data, RFC_SECRET);
+ p2.setTypeSecret();
+ stick10_08::SendOTPData::CommandTransaction::run(stick, p2);
+
+ auto p = get_payload<WriteToOTPSlot>();
+ strcpyT(p.temporary_admin_password, temporary_password);
+ p.use_8_digits = true;
+ p.slot_number = 0 + 0x10;
+ p.slot_counter_or_interval = 0;
+ stick10_08::WriteToOTPSlot::CommandTransaction::run(stick, p);
+
+
+ auto p3 = get_payload<GetHOTP>();
+ p3.slot_number = 0 + 0x10;
+ REQUIRE_THROWS(
+ GetHOTP::CommandTransaction::run(stick, p3)
+ );
+ strcpyT(p3.temporary_user_password, temporary_password);
+ auto code_response = GetHOTP::CommandTransaction::run(stick, p3);
+ REQUIRE(code_response.data().code == 84755224);
+
+}
+
+TEST_CASE("check firmware version", "[pronew]") {
+ auto stick = make_shared<Dev>();
+ connect_and_setup(stick);
+
+ auto p = GetStatus::CommandTransaction::run(stick);
+ REQUIRE(p.data().firmware_version == 8);
+}
+
+TEST_CASE("authorize user TOTP", "[pronew]") {
+ auto stick = make_shared<Dev>();
+ connect_and_setup(stick);
+ authorize(stick);
+
+ {
+ auto p = get_payload<WriteGeneralConfig>();
+ p.enable_user_password = 1;
+ strcpyT(p.temporary_admin_password, temporary_password);
+ WriteGeneralConfig::CommandTransaction::run(stick, p);
+ }
+
+ auto p2 = get_payload<SendOTPData>();
+ strcpyT(p2.temporary_admin_password, temporary_password);
+ p2.setTypeName();
+ strcpyT(p2.data, "test name TOTP");
+ stick10_08::SendOTPData::CommandTransaction::run(stick, p2);
+
+ p2 = get_payload<SendOTPData>();
+ strcpyT(p2.temporary_admin_password, temporary_password);
+ strcpyT(p2.data, RFC_SECRET);
+ p2.setTypeSecret();
+ stick10_08::SendOTPData::CommandTransaction::run(stick, p2);
+
+ auto p = get_payload<WriteToOTPSlot>();
+ strcpyT(p.temporary_admin_password, temporary_password);
+ p.use_8_digits = true;
+ p.slot_number = 0 + 0x20;
+ p.slot_counter_or_interval = 30;
+ stick10_08::WriteToOTPSlot::CommandTransaction::run(stick, p);
+
+ auto p_get_totp = get_payload<GetTOTP>();
+ p_get_totp.slot_number = 0 + 0x20;
+
+ REQUIRE_THROWS(
+ GetTOTP::CommandTransaction::run(stick, p_get_totp)
+ );
+ strcpyT(p_get_totp.temporary_user_password, temporary_password);
+
+ auto p_set_time = get_payload<SetTime>();
+ p_set_time.reset = 1;
+ p_set_time.time = 59;
+ SetTime::CommandTransaction::run(stick, p_set_time);
+ auto code = GetTOTP::CommandTransaction::run(stick, p_get_totp);
+ REQUIRE(code.data().code == 94287082);
+
+}
diff --git a/unittest/test_C_API.cpp b/unittest/test_C_API.cpp
new file mode 100644
index 0000000..160145b
--- /dev/null
+++ b/unittest/test_C_API.cpp
@@ -0,0 +1,34 @@
+static const int TOO_LONG_STRING = 200;
+
+#include "catch.hpp"
+
+#include <iostream>
+#include <string>
+#include "log.h"
+#include "../NK_C_API.h"
+
+TEST_CASE("C API connect", "[BASIC]") {
+ auto login = NK_login_auto();
+ REQUIRE(login != 0);
+ NK_logout();
+ login = NK_login_auto();
+ REQUIRE(login != 0);
+ NK_logout();
+ login = NK_login_auto();
+ REQUIRE(login != 0);
+}
+
+TEST_CASE("Check retry count", "[BASIC]") {
+ REQUIRE(NK_get_admin_retry_count() == 3);
+ REQUIRE(NK_get_user_retry_count() == 3);
+}
+
+TEST_CASE("Check long strings", "[STANDARD]") {
+ const char* longPin = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+ const char* pin = "123123123";
+ auto result = NK_change_user_PIN(longPin, pin);
+ REQUIRE(result == TOO_LONG_STRING);
+ result = NK_change_user_PIN(pin, longPin);
+ REQUIRE(result == TOO_LONG_STRING);
+ CAPTURE(result);
+} \ No newline at end of file
diff --git a/unittest/test_HOTP.cc b/unittest/test_HOTP.cc
new file mode 100644
index 0000000..e6f7d7c
--- /dev/null
+++ b/unittest/test_HOTP.cc
@@ -0,0 +1,101 @@
+#include "catch.hpp"
+#include <iostream>
+#include "device_proto.h"
+#include "log.h"
+#include "stick10_commands.h"
+#include <cstdlib>
+#include "misc.h"
+
+using namespace std;
+using namespace nitrokey::device;
+using namespace nitrokey::proto::stick10;
+using namespace nitrokey::log;
+using namespace nitrokey::misc;
+
+void hexStringToByte(uint8_t data[], const char* hexString){
+ REQUIRE(strlen(hexString)%2==0);
+ char buf[2];
+ for(int i=0; i<strlen(hexString); i++){
+ buf[i%2] = hexString[i];
+ if (i%2==1){
+ data[i/2] = strtoul(buf, NULL, 16) & 0xFF;
+ }
+ }
+};
+
+TEST_CASE("test secret", "[functions]") {
+ uint8_t slot_secret[21];
+ slot_secret[20] = 0;
+ const char* secretHex = "3132333435363738393031323334353637383930";
+ hexStringToByte(slot_secret, secretHex);
+ CAPTURE(slot_secret);
+ REQUIRE(strcmp("12345678901234567890",reinterpret_cast<char *>(slot_secret) ) == 0 );
+}
+
+TEST_CASE("Test HOTP codes according to RFC", "[HOTP]") {
+ std::shared_ptr<Stick10> stick = make_shared<Stick10>();
+ bool connected = stick->connect();
+
+ REQUIRE(connected == true);
+
+ Log::instance().set_loglevel(Loglevel::DEBUG);
+
+ auto resp = GetStatus::CommandTransaction::run(stick);
+
+ const char * temporary_password = "123456789012345678901234";
+ {
+ auto authreq = get_payload<FirstAuthenticate>();
+ strcpy((char *)(authreq.card_password), "12345678");
+ strcpy((char *)(authreq.temporary_password), temporary_password);
+ FirstAuthenticate::CommandTransaction::run(stick, authreq);
+ }
+
+ //test according to https://tools.ietf.org/html/rfc4226#page-32
+ {
+ auto hwrite = get_payload<WriteToHOTPSlot>();
+ hwrite.slot_number = 0x10;
+ strcpy(reinterpret_cast<char *>(hwrite.slot_name), "rfc4226_lib");
+ //strcpy(reinterpret_cast<char *>(hwrite.slot_secret), "");
+ const char* secretHex = "3132333435363738393031323334353637383930";
+ hexStringToByte(hwrite.slot_secret, secretHex);
+
+ //hwrite.slot_config; //TODO check various configs in separate test cases
+ //strcpy(reinterpret_cast<char *>(hwrite.slot_token_id), "");
+ //strcpy(reinterpret_cast<char *>(hwrite.slot_counter), "");
+
+ //authorize writehotp first
+ {
+ auto auth = get_payload<Authorize>();
+ strcpy((char *)(auth.temporary_password), temporary_password);
+ auth.crc_to_authorize = WriteToHOTPSlot::CommandTransaction::getCRC(hwrite);
+ Authorize::CommandTransaction::run(stick, auth);
+ }
+
+ //run hotp command
+ WriteToHOTPSlot::CommandTransaction::run(stick, hwrite);
+
+ uint32_t codes[] = {
+ 755224, 287082, 359152, 969429, 338314,
+ 254676, 287922, 162583, 399871, 520489
+ };
+
+ for( auto code: codes){
+ auto gh = get_payload<GetHOTP>();
+ gh.slot_number = 0x10;
+ auto resp = GetHOTP::CommandTransaction::run(stick, gh);
+ REQUIRE( resp.data().code == code);
+ }
+ //checking slot programmed before with nitro-app
+ /*
+ for( auto code: codes){
+ GetHOTP::CommandTransaction::CommandPayload gh;
+ gh.slot_number = 0x12;
+ auto resp = GetHOTP::CommandTransaction::run(stick, gh);
+ REQUIRE( resp.code == code);
+ }
+ */
+ }
+
+
+ stick->disconnect();
+}
diff --git a/unittest/test_command_ids_header.h b/unittest/test_command_ids_header.h
new file mode 100644
index 0000000..cd55c8a
--- /dev/null
+++ b/unittest/test_command_ids_header.h
@@ -0,0 +1,41 @@
+#ifndef LIBNITROKEY_TEST_COMMAND_IDS_HEADER_H_H
+#define LIBNITROKEY_TEST_COMMAND_IDS_HEADER_H_H
+
+#define STICK20_CMD_START_VALUE 0x20
+#define STICK20_CMD_ENABLE_CRYPTED_PARI (STICK20_CMD_START_VALUE + 0)
+#define STICK20_CMD_DISABLE_CRYPTED_PARI (STICK20_CMD_START_VALUE + 1)
+#define STICK20_CMD_ENABLE_HIDDEN_CRYPTED_PARI (STICK20_CMD_START_VALUE + 2)
+#define STICK20_CMD_DISABLE_HIDDEN_CRYPTED_PARI (STICK20_CMD_START_VALUE + 3)
+#define STICK20_CMD_ENABLE_FIRMWARE_UPDATE (STICK20_CMD_START_VALUE + 4)
+#define STICK20_CMD_EXPORT_FIRMWARE_TO_FILE (STICK20_CMD_START_VALUE + 5)
+#define STICK20_CMD_GENERATE_NEW_KEYS (STICK20_CMD_START_VALUE + 6)
+#define STICK20_CMD_FILL_SD_CARD_WITH_RANDOM_CHARS (STICK20_CMD_START_VALUE + 7)
+
+#define STICK20_CMD_WRITE_STATUS_DATA (STICK20_CMD_START_VALUE + 8)
+#define STICK20_CMD_ENABLE_READONLY_UNCRYPTED_LUN (STICK20_CMD_START_VALUE + 9)
+#define STICK20_CMD_ENABLE_READWRITE_UNCRYPTED_LUN (STICK20_CMD_START_VALUE + 10)
+
+#define STICK20_CMD_SEND_PASSWORD_MATRIX (STICK20_CMD_START_VALUE + 11)
+#define STICK20_CMD_SEND_PASSWORD_MATRIX_PINDATA (STICK20_CMD_START_VALUE + 12)
+#define STICK20_CMD_SEND_PASSWORD_MATRIX_SETUP (STICK20_CMD_START_VALUE + 13)
+
+#define STICK20_CMD_GET_DEVICE_STATUS (STICK20_CMD_START_VALUE + 14)
+#define STICK20_CMD_SEND_DEVICE_STATUS (STICK20_CMD_START_VALUE + 15)
+
+#define STICK20_CMD_SEND_HIDDEN_VOLUME_PASSWORD (STICK20_CMD_START_VALUE + 16)
+#define STICK20_CMD_SEND_HIDDEN_VOLUME_SETUP (STICK20_CMD_START_VALUE + 17)
+#define STICK20_CMD_SEND_PASSWORD (STICK20_CMD_START_VALUE + 18)
+#define STICK20_CMD_SEND_NEW_PASSWORD (STICK20_CMD_START_VALUE + 19)
+#define STICK20_CMD_CLEAR_NEW_SD_CARD_FOUND (STICK20_CMD_START_VALUE + 20)
+
+#define STICK20_CMD_SEND_STARTUP (STICK20_CMD_START_VALUE + 21)
+#define STICK20_CMD_SEND_CLEAR_STICK_KEYS_NOT_INITIATED (STICK20_CMD_START_VALUE + 22)
+#define STICK20_CMD_SEND_LOCK_STICK_HARDWARE (STICK20_CMD_START_VALUE + 23)
+
+#define STICK20_CMD_PRODUCTION_TEST (STICK20_CMD_START_VALUE + 24)
+#define STICK20_CMD_SEND_DEBUG_DATA (STICK20_CMD_START_VALUE + 25)
+
+#define STICK20_CMD_CHANGE_UPDATE_PIN (STICK20_CMD_START_VALUE + 26)
+
+
+#endif //LIBNITROKEY_TEST_COMMAND_IDS_HEADER_H_H
diff --git a/unittest/test_issues.cc b/unittest/test_issues.cc
new file mode 100644
index 0000000..63ce678
--- /dev/null
+++ b/unittest/test_issues.cc
@@ -0,0 +1,78 @@
+
+const char * const default_admin_pin = "12345678";
+const char * const default_user_pin = "123456";
+const char * const temporary_password = "123456789012345678901234";
+const char * const RFC_SECRET = "12345678901234567890";
+const char * const hidden_volume_pass = "123456789012345";
+
+#include "catch.hpp"
+
+#include <NitrokeyManager.h>
+
+using namespace std;
+using namespace nitrokey;
+
+
+bool test_36(){
+ auto i = NitrokeyManager::instance();
+ i->set_loglevel(3);
+ REQUIRE(i->connect());
+
+ for (int j = 0; j < 200; ++j) {
+ i->get_status();
+ i->get_status_storage_as_string();
+ INFO( "Iteration: " << j);
+ }
+ return true;
+}
+
+bool test_31(){
+ auto i = NitrokeyManager::instance();
+ i->set_loglevel(3);
+ REQUIRE(i->connect());
+
+// i->unlock_encrypted_volume(default_user_pin);
+// i->create_hidden_volume(0, 70, 80, hidden_volume_pass);
+// i->lock_device();
+
+ try{
+ i->get_password_safe_slot_status();
+ }
+ catch (...){
+ //pass
+ }
+
+ i->get_status_storage();
+ i->get_admin_retry_count();
+ i->get_status_storage();
+ i->get_user_retry_count();
+ i->unlock_encrypted_volume(default_user_pin);
+ i->get_status_storage();
+ i->get_password_safe_slot_status();
+ i->get_status_storage();
+ i->get_user_retry_count();
+ i->get_password_safe_slot_status();
+ i->get_status();
+ i->get_status_storage();
+ i->get_admin_retry_count();
+ i->get_status();
+ i->get_user_retry_count();
+ i->unlock_hidden_volume(hidden_volume_pass);
+ i->get_status_storage();
+ i->get_password_safe_slot_status();
+
+
+ return true;
+}
+
+TEST_CASE("issue 31", "[issue]"){
+ for(int i=0; i<20; i++){
+ REQUIRE(test_31());
+ }
+}
+
+
+
+TEST_CASE("issue 36", "[issue]"){
+ REQUIRE(test_36());
+}
diff --git a/unittest/test_library.py b/unittest/test_library.py
new file mode 100644
index 0000000..7012619
--- /dev/null
+++ b/unittest/test_library.py
@@ -0,0 +1,68 @@
+import pytest
+
+from misc import ffi, gs, to_hex, is_pro_rtm_07, is_long_OTP_secret_handled
+from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET, LibraryErrors, bbRFC_SECRET
+
+
+def test_too_long_strings(C):
+ new_password = b'123123123'
+ long_string = b'a' * 100
+ assert C.NK_change_user_PIN(long_string, new_password) == LibraryErrors.TOO_LONG_STRING
+ assert C.NK_change_user_PIN(new_password, long_string) == LibraryErrors.TOO_LONG_STRING
+ assert C.NK_change_admin_PIN(long_string, new_password) == LibraryErrors.TOO_LONG_STRING
+ assert C.NK_change_admin_PIN(new_password, long_string) == LibraryErrors.TOO_LONG_STRING
+ assert C.NK_first_authenticate(long_string, DefaultPasswords.ADMIN_TEMP) == LibraryErrors.TOO_LONG_STRING
+ assert C.NK_erase_totp_slot(0, long_string) == LibraryErrors.TOO_LONG_STRING
+ digits = False
+ assert C.NK_write_hotp_slot(1, long_string, bbRFC_SECRET, 0, digits, False, False, b"",
+ DefaultPasswords.ADMIN_TEMP) == LibraryErrors.TOO_LONG_STRING
+ assert C.NK_write_hotp_slot(1, b'long_test', bbRFC_SECRET, 0, digits, False, False, b"",
+ long_string) == LibraryErrors.TOO_LONG_STRING
+ assert gs(C.NK_get_hotp_code_PIN(0, long_string)) == b""
+ assert C.NK_get_last_command_status() == LibraryErrors.TOO_LONG_STRING
+
+
+def test_invalid_slot(C):
+ invalid_slot = 255
+ assert C.NK_erase_totp_slot(invalid_slot, b'some password') == LibraryErrors.INVALID_SLOT
+ assert C.NK_write_hotp_slot(invalid_slot, b'long_test', bbRFC_SECRET, 0, False, False, False, b"",
+ b'aaa') == LibraryErrors.INVALID_SLOT
+ assert gs(C.NK_get_hotp_code_PIN(invalid_slot, b'some password')) == b""
+ assert C.NK_get_last_command_status() == LibraryErrors.INVALID_SLOT
+ assert C.NK_erase_password_safe_slot(invalid_slot) == LibraryErrors.INVALID_SLOT
+ assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ assert gs(C.NK_get_password_safe_slot_name(invalid_slot)) == b''
+ assert C.NK_get_last_command_status() == LibraryErrors.INVALID_SLOT
+ assert gs(C.NK_get_password_safe_slot_login(invalid_slot)) == b''
+ assert C.NK_get_last_command_status() == LibraryErrors.INVALID_SLOT
+
+
+@pytest.mark.parametrize("invalid_hex_string",
+ ['text', '00 ', '0xff', 'zzzzzzzzzzzz', 'fff', 'f' * 257, 'f' * 258])
+def test_invalid_secret_hex_string_for_OTP_write(C, invalid_hex_string):
+ """
+ Tests for invalid secret hex string during writing to OTP slot. Invalid strings are not hexadecimal number,
+ empty or longer than 255 characters.
+ """
+ invalid_hex_string = invalid_hex_string.encode('ascii')
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_hotp_slot(1, b'slot_name', invalid_hex_string, 0, True, False, False, b'',
+ DefaultPasswords.ADMIN_TEMP) == LibraryErrors.INVALID_HEX_STRING
+ assert C.NK_write_totp_slot(1, b'python_test', invalid_hex_string, 30, True, False, False, b"",
+ DefaultPasswords.ADMIN_TEMP) == LibraryErrors.INVALID_HEX_STRING
+
+def test_warning_binary_bigger_than_secret_buffer(C):
+ invalid_hex_string = to_hex('1234567890') * 3
+ invalid_hex_string = invalid_hex_string.encode('ascii')
+ if is_long_OTP_secret_handled(C):
+ invalid_hex_string *= 2
+ assert C.NK_write_hotp_slot(1, b'slot_name', invalid_hex_string, 0, True, False, False, b'',
+ DefaultPasswords.ADMIN_TEMP) == LibraryErrors.TARGET_BUFFER_SIZE_SMALLER_THAN_SOURCE
+
+
+@pytest.mark.skip(reason='Experimental')
+def test_clear(C):
+ d = 'asdasdasd'
+ print(d)
+ C.clear_password(d)
+ print(d) \ No newline at end of file
diff --git a/unittest/test_offline.cc b/unittest/test_offline.cc
new file mode 100644
index 0000000..58c7555
--- /dev/null
+++ b/unittest/test_offline.cc
@@ -0,0 +1,141 @@
+#include "catch.hpp"
+#include <NitrokeyManager.h>
+#include <memory>
+#include "../NK_C_API.h"
+
+using namespace nitrokey::proto;
+using namespace nitrokey::device;
+
+using namespace std;
+using namespace nitrokey;
+
+//This test suite assumes no Pro or Storage devices are connected
+
+TEST_CASE("Return false on no device connected", "[fast]") {
+ INFO("This test case assumes no Pro or Storage devices are connected");
+ auto stick = make_shared<Stick20>();
+ bool connected = true;
+ REQUIRE_NOTHROW(connected = stick->connect());
+ REQUIRE_FALSE(connected);
+
+ auto stick_pro = make_shared<Stick10>();
+ REQUIRE_NOTHROW(connected = stick_pro->connect());
+ REQUIRE_FALSE(connected);
+
+
+ auto i = NitrokeyManager::instance();
+ REQUIRE_NOTHROW(connected = i->connect());
+ REQUIRE_FALSE(connected);
+ REQUIRE_FALSE(i->is_connected());
+ REQUIRE_FALSE(i->disconnect());
+ REQUIRE_FALSE(i->could_current_device_be_enumerated());
+
+
+ int C_connected = 1;
+ REQUIRE_NOTHROW(C_connected = NK_login_auto());
+ REQUIRE(0 == C_connected);
+}
+
+TEST_CASE("Test C++ side behaviour in offline", "[fast]") {
+ auto i = NitrokeyManager::instance();
+
+ string serial_number;
+ REQUIRE_NOTHROW (serial_number = i->get_serial_number());
+ REQUIRE(serial_number.empty());
+
+ REQUIRE_THROWS_AS(
+ i->get_status(), DeviceNotConnected
+ );
+
+ REQUIRE_THROWS_AS(
+ i->get_HOTP_code(0xFF, ""), InvalidSlotException
+ );
+
+ REQUIRE_THROWS_AS(
+ i->get_TOTP_code(0xFF, ""), InvalidSlotException
+ );
+
+ REQUIRE_THROWS_AS(
+ i->erase_hotp_slot(0xFF, ""), InvalidSlotException
+ );
+
+ REQUIRE_THROWS_AS(
+ i->erase_totp_slot(0xFF, ""), InvalidSlotException
+ );
+
+ REQUIRE_THROWS_AS(
+ i->get_totp_slot_name(0xFF), InvalidSlotException
+ );
+
+ REQUIRE_THROWS_AS(
+ i->get_hotp_slot_name(0xFF), InvalidSlotException
+ );
+
+ REQUIRE_THROWS_AS(
+ i->first_authenticate("123123", "123123"), DeviceNotConnected
+ );
+
+ REQUIRE_THROWS_AS(
+ i->get_connected_device_model(), DeviceNotConnected
+ );
+
+ REQUIRE_THROWS_AS(
+ i->clear_new_sd_card_warning("123123"), DeviceNotConnected
+ );
+
+}
+
+
+TEST_CASE("Test helper function - hex_string_to_byte", "[fast]") {
+ using namespace nitrokey::misc;
+ std::vector<uint8_t> v;
+ REQUIRE_NOTHROW(v = hex_string_to_byte("00112233445566"));
+ const uint8_t test_data[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66};
+ REQUIRE(v.size() == sizeof(test_data));
+ for (int i = 0; i < v.size(); ++i) {
+ INFO("Position i: " << i);
+ REQUIRE(v.data()[i] == test_data[i]);
+ }
+}
+
+#include "test_command_ids_header.h"
+TEST_CASE("Test device commands ids", "[fast]") {
+// Make sure CommandID values are in sync with firmware's header
+
+// REQUIRE(STICK20_CMD_START_VALUE == static_cast<uint8_t>(CommandID::START_VALUE));
+ REQUIRE(STICK20_CMD_ENABLE_CRYPTED_PARI == static_cast<uint8_t>(CommandID::ENABLE_CRYPTED_PARI));
+ REQUIRE(STICK20_CMD_DISABLE_CRYPTED_PARI == static_cast<uint8_t>(CommandID::DISABLE_CRYPTED_PARI));
+ REQUIRE(STICK20_CMD_ENABLE_HIDDEN_CRYPTED_PARI == static_cast<uint8_t>(CommandID::ENABLE_HIDDEN_CRYPTED_PARI));
+ REQUIRE(STICK20_CMD_DISABLE_HIDDEN_CRYPTED_PARI == static_cast<uint8_t>(CommandID::DISABLE_HIDDEN_CRYPTED_PARI));
+ REQUIRE(STICK20_CMD_ENABLE_FIRMWARE_UPDATE == static_cast<uint8_t>(CommandID::ENABLE_FIRMWARE_UPDATE));
+ REQUIRE(STICK20_CMD_EXPORT_FIRMWARE_TO_FILE == static_cast<uint8_t>(CommandID::EXPORT_FIRMWARE_TO_FILE));
+ REQUIRE(STICK20_CMD_GENERATE_NEW_KEYS == static_cast<uint8_t>(CommandID::GENERATE_NEW_KEYS));
+ REQUIRE(STICK20_CMD_FILL_SD_CARD_WITH_RANDOM_CHARS == static_cast<uint8_t>(CommandID::FILL_SD_CARD_WITH_RANDOM_CHARS));
+
+ REQUIRE(STICK20_CMD_WRITE_STATUS_DATA == static_cast<uint8_t>(CommandID::WRITE_STATUS_DATA));
+ REQUIRE(STICK20_CMD_ENABLE_READONLY_UNCRYPTED_LUN == static_cast<uint8_t>(CommandID::ENABLE_READONLY_UNCRYPTED_LUN));
+ REQUIRE(STICK20_CMD_ENABLE_READWRITE_UNCRYPTED_LUN == static_cast<uint8_t>(CommandID::ENABLE_READWRITE_UNCRYPTED_LUN));
+
+ REQUIRE(STICK20_CMD_SEND_PASSWORD_MATRIX == static_cast<uint8_t>(CommandID::SEND_PASSWORD_MATRIX));
+ REQUIRE(STICK20_CMD_SEND_PASSWORD_MATRIX_PINDATA == static_cast<uint8_t>(CommandID::SEND_PASSWORD_MATRIX_PINDATA));
+ REQUIRE(STICK20_CMD_SEND_PASSWORD_MATRIX_SETUP == static_cast<uint8_t>(CommandID::SEND_PASSWORD_MATRIX_SETUP));
+
+ REQUIRE(STICK20_CMD_GET_DEVICE_STATUS == static_cast<uint8_t>(CommandID::GET_DEVICE_STATUS));
+ REQUIRE(STICK20_CMD_SEND_DEVICE_STATUS == static_cast<uint8_t>(CommandID::SEND_DEVICE_STATUS));
+
+ REQUIRE(STICK20_CMD_SEND_HIDDEN_VOLUME_PASSWORD == static_cast<uint8_t>(CommandID::SEND_HIDDEN_VOLUME_PASSWORD));
+ REQUIRE(STICK20_CMD_SEND_HIDDEN_VOLUME_SETUP == static_cast<uint8_t>(CommandID::SEND_HIDDEN_VOLUME_SETUP));
+ REQUIRE(STICK20_CMD_SEND_PASSWORD == static_cast<uint8_t>(CommandID::SEND_PASSWORD));
+ REQUIRE(STICK20_CMD_SEND_NEW_PASSWORD == static_cast<uint8_t>(CommandID::SEND_NEW_PASSWORD));
+ REQUIRE(STICK20_CMD_CLEAR_NEW_SD_CARD_FOUND == static_cast<uint8_t>(CommandID::CLEAR_NEW_SD_CARD_FOUND));
+
+ REQUIRE(STICK20_CMD_SEND_STARTUP == static_cast<uint8_t>(CommandID::SEND_STARTUP));
+ REQUIRE(STICK20_CMD_SEND_CLEAR_STICK_KEYS_NOT_INITIATED == static_cast<uint8_t>(CommandID::SEND_CLEAR_STICK_KEYS_NOT_INITIATED));
+ REQUIRE(STICK20_CMD_SEND_LOCK_STICK_HARDWARE == static_cast<uint8_t>(CommandID::SEND_LOCK_STICK_HARDWARE));
+
+ REQUIRE(STICK20_CMD_PRODUCTION_TEST == static_cast<uint8_t>(CommandID::PRODUCTION_TEST));
+ REQUIRE(STICK20_CMD_SEND_DEBUG_DATA == static_cast<uint8_t>(CommandID::SEND_DEBUG_DATA));
+
+ REQUIRE(STICK20_CMD_CHANGE_UPDATE_PIN == static_cast<uint8_t>(CommandID::CHANGE_UPDATE_PIN));
+
+}
diff --git a/unittest/test_pro.py b/unittest/test_pro.py
new file mode 100644
index 0000000..830946d
--- /dev/null
+++ b/unittest/test_pro.py
@@ -0,0 +1,853 @@
+import pytest
+
+from conftest import skip_if_device_version_lower_than
+from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET, bb, bbRFC_SECRET
+from misc import ffi, gs, wait, cast_pointer_to_tuple
+from misc import is_pro_rtm_07, is_pro_rtm_08, is_storage
+
+@pytest.mark.lock_device
+@pytest.mark.PWS
+def test_enable_password_safe(C):
+ """
+ All Password Safe tests depend on AES keys being initialized. They will fail otherwise.
+ """
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_enable_password_safe(b'wrong_password') == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+
+@pytest.mark.lock_device
+@pytest.mark.PWS
+def test_write_password_safe_slot(C):
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_password_safe_slot(0, b'slotname1', b'login1', b'pass1') == DeviceErrorCode.STATUS_NOT_AUTHORIZED
+ assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_password_safe_slot(0, b'slotname1', b'login1', b'pass1') == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.lock_device
+@pytest.mark.PWS
+@pytest.mark.slowtest
+def test_write_all_password_safe_slots_and_read_10_times(C):
+ def fill(s, wid):
+ assert wid >= len(s)
+ numbers = '1234567890'*4
+ s += numbers[:wid-len(s)]
+ assert len(s) == wid
+ return bb(s)
+
+ def get_pass(suffix):
+ return fill('pass' + suffix, 20)
+
+ def get_loginname(suffix):
+ return fill('login' + suffix, 32)
+
+ def get_slotname(suffix):
+ return fill('slotname' + suffix, 11)
+
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ PWS_slot_count = 16
+ for i in range(0, PWS_slot_count):
+ iss = str(i)
+ assert C.NK_write_password_safe_slot(i,
+ get_slotname(iss), get_loginname(iss),
+ get_pass(iss)) == DeviceErrorCode.STATUS_OK
+
+ for j in range(0, 10):
+ for i in range(0, PWS_slot_count):
+ iss = str(i)
+ assert gs(C.NK_get_password_safe_slot_name(i)) == get_slotname(iss)
+ assert gs(C.NK_get_password_safe_slot_login(i)) == get_loginname(iss)
+ assert gs(C.NK_get_password_safe_slot_password(i)) == get_pass(iss)
+
+
+@pytest.mark.lock_device
+@pytest.mark.PWS
+@pytest.mark.slowtest
+@pytest.mark.xfail(reason="This test should be run directly after test_write_all_password_safe_slots_and_read_10_times")
+def test_read_all_password_safe_slots_10_times(C):
+ def fill(s, wid):
+ assert wid >= len(s)
+ numbers = '1234567890'*4
+ s += numbers[:wid-len(s)]
+ assert len(s) == wid
+ return bb(s)
+
+ def get_pass(suffix):
+ return fill('pass' + suffix, 20)
+
+ def get_loginname(suffix):
+ return fill('login' + suffix, 32)
+
+ def get_slotname(suffix):
+ return fill('slotname' + suffix, 11)
+
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ PWS_slot_count = 16
+
+ for j in range(0, 10):
+ for i in range(0, PWS_slot_count):
+ iss = str(i)
+ assert gs(C.NK_get_password_safe_slot_name(i)) == get_slotname(iss)
+ assert gs(C.NK_get_password_safe_slot_login(i)) == get_loginname(iss)
+ assert gs(C.NK_get_password_safe_slot_password(i)) == get_pass(iss)
+
+
+@pytest.mark.lock_device
+@pytest.mark.PWS
+def test_get_password_safe_slot_name(C):
+ assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_password_safe_slot(0, b'slotname1', b'login1', b'pass1') == DeviceErrorCode.STATUS_OK
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert gs(C.NK_get_password_safe_slot_name(0)) == b''
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_NOT_AUTHORIZED
+
+ 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
+
+
+@pytest.mark.PWS
+def test_get_password_safe_slot_login_password(C):
+ assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_password_safe_slot(0, b'slotname1', b'login1', b'pass1') == 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'
+ slot_password = gs(C.NK_get_password_safe_slot_password(0))
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+ assert slot_password == b'pass1'
+
+
+@pytest.mark.PWS
+def test_erase_password_safe_slot(C):
+ assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ assert C.NK_erase_password_safe_slot(0) == DeviceErrorCode.STATUS_OK
+ assert gs(C.NK_get_password_safe_slot_name(0)) == b''
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK # TODO CHECK shouldn't this be DeviceErrorCode.NOT_PROGRAMMED ?
+
+
+@pytest.mark.PWS
+def test_password_safe_slot_status(C):
+ assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ assert C.NK_erase_password_safe_slot(0) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_password_safe_slot(1, b'slotname2', b'login2', b'pass2') == DeviceErrorCode.STATUS_OK
+ safe_slot_status = C.NK_get_password_safe_slot_status()
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+ is_slot_programmed = list(ffi.cast("uint8_t [16]", safe_slot_status)[0:16])
+ print((is_slot_programmed, len(is_slot_programmed)))
+ assert is_slot_programmed[0] == 0
+ assert is_slot_programmed[1] == 1
+
+
+@pytest.mark.aes
+def test_issue_device_locks_on_second_key_generation_in_sequence(C):
+ if is_pro_rtm_07(C) or is_pro_rtm_08(C):
+ pytest.skip("issue to register: device locks up "
+ "after below commands sequence (reinsertion fixes), skipping for now")
+ assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+ assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.aes
+def test_regenerate_aes_key(C):
+ 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_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.lock_device
+@pytest.mark.aes
+@pytest.mark.factory_reset
+def test_enable_password_safe_after_factory_reset(C):
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ if is_storage(C):
+ # for some reason storage likes to be authenticated before reset (to investigate)
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_factory_reset(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+ wait(10)
+ 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 == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+ assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+
+@pytest.mark.lock_device
+@pytest.mark.aes
+@pytest.mark.xfail(reason="NK Pro firmware bug: regenerating AES key command not always results in cleared slot data")
+def test_destroy_password_safe(C):
+ """
+ Sometimes fails on NK Pro - slot name is not cleared ergo key generation has not succeed despite the success result
+ returned from the device
+ """
+ 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'
+ # 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
+
+ # check was slot status cleared
+ safe_slot_status = C.NK_get_password_safe_slot_status()
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+ is_slot_programmed = list(ffi.cast("uint8_t [16]", safe_slot_status)[0:16])
+ assert is_slot_programmed[0] == 0
+
+
+@pytest.mark.aes
+def test_is_AES_supported(C):
+ if is_storage(C):
+ pytest.skip("Storage does not implement this command")
+ assert C.NK_is_AES_supported(b'wrong password') != 1
+ assert C.NK_get_last_command_status() == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_is_AES_supported(DefaultPasswords.USER) == 1
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.pin
+def test_admin_PIN_change(C):
+ new_password = b'123123123'
+ assert C.NK_change_admin_PIN(b'wrong_password', new_password) == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_change_admin_PIN(DefaultPasswords.ADMIN, new_password) == DeviceErrorCode.STATUS_OK
+ assert C.NK_change_admin_PIN(new_password, DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.pin
+def test_user_PIN_change(C):
+ new_password = b'123123123'
+ assert C.NK_change_user_PIN(b'wrong_password', new_password) == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_change_user_PIN(DefaultPasswords.USER, new_password) == DeviceErrorCode.STATUS_OK
+ assert C.NK_change_user_PIN(new_password, DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.lock_device
+@pytest.mark.pin
+def test_admin_retry_counts(C):
+ default_admin_retry_count = 3
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_get_admin_retry_count() == default_admin_retry_count
+ assert C.NK_change_admin_PIN(b'wrong_password', DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_get_admin_retry_count() == default_admin_retry_count - 1
+ assert C.NK_change_admin_PIN(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+ assert C.NK_get_admin_retry_count() == default_admin_retry_count
+
+
+@pytest.mark.lock_device
+@pytest.mark.pin
+def test_user_retry_counts_change_PIN(C):
+ assert C.NK_change_user_PIN(DefaultPasswords.USER, DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ wrong_password = b'wrong_password'
+ default_user_retry_count = 3
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_get_user_retry_count() == default_user_retry_count
+ assert C.NK_change_user_PIN(wrong_password, wrong_password) == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_get_user_retry_count() == default_user_retry_count - 1
+ assert C.NK_change_user_PIN(DefaultPasswords.USER, DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ assert C.NK_get_user_retry_count() == default_user_retry_count
+
+
+@pytest.mark.lock_device
+@pytest.mark.pin
+def test_user_retry_counts_PWSafe(C):
+ default_user_retry_count = 3
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_get_user_retry_count() == default_user_retry_count
+ assert C.NK_enable_password_safe(b'wrong_password') == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_get_user_retry_count() == default_user_retry_count - 1
+ assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ assert C.NK_get_user_retry_count() == default_user_retry_count
+
+
+@pytest.mark.pin
+def test_unlock_user_password(C):
+ default_user_retry_count = 3
+ default_admin_retry_count = 3
+ new_password = b'123123123'
+ assert C.NK_get_user_retry_count() == default_user_retry_count
+ assert C.NK_change_user_PIN(b'wrong_password', new_password) == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_change_user_PIN(b'wrong_password', new_password) == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_change_user_PIN(b'wrong_password', new_password) == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_get_user_retry_count() == default_user_retry_count - 3
+ assert C.NK_get_admin_retry_count() == default_admin_retry_count
+
+ assert C.NK_unlock_user_password(b'wrong password', DefaultPasswords.USER) == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_get_admin_retry_count() == default_admin_retry_count - 1
+ assert C.NK_unlock_user_password(DefaultPasswords.ADMIN, DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ assert C.NK_get_user_retry_count() == default_user_retry_count
+ assert C.NK_get_admin_retry_count() == default_admin_retry_count
+
+
+@pytest.mark.pin
+def test_admin_auth(C):
+ assert C.NK_first_authenticate(b'wrong_password', DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.pin
+def test_user_auth(C):
+ assert C.NK_user_authenticate(b'wrong_password', DefaultPasswords.USER_TEMP) == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP) == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.otp
+def check_HOTP_RFC_codes(C, func, prep=None, use_8_digits=False):
+ """
+ # https://tools.ietf.org/html/rfc4226#page-32
+ """
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_hotp_slot(1, b'python_test', bbRFC_SECRET, 0, use_8_digits, False, False, b'',
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ test_data = [
+ 1284755224, 1094287082, 137359152, 1726969429, 1640338314, 868254676, 1918287922, 82162583, 673399871,
+ 645520489,
+ ]
+ for code in test_data:
+ if prep:
+ prep()
+ r = func(1)
+ code = str(code)[-8:] if use_8_digits else str(code)[-6:]
+ assert bb(code) == r
+
+
+@pytest.mark.otp
+@pytest.mark.parametrize("use_8_digits", [False, True, ])
+@pytest.mark.parametrize("use_pin_protection", [False, True, ])
+def test_HOTP_RFC_use8digits_usepin(C, use_8_digits, use_pin_protection):
+ 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
+ if use_pin_protection:
+ check_HOTP_RFC_codes(C,
+ lambda x: gs(C.NK_get_hotp_code_PIN(x, DefaultPasswords.USER_TEMP)),
+ lambda: C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP),
+ use_8_digits=use_8_digits)
+ else:
+ check_HOTP_RFC_codes(C, lambda x: gs(C.NK_get_hotp_code(x)), use_8_digits=use_8_digits)
+
+
+@pytest.mark.otp
+def test_HOTP_token(C):
+ """
+ Check HOTP routine with written token ID to slot.
+ """
+ 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
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ token_ID = b"AAV100000022"
+ assert C.NK_write_hotp_slot(1, b'python_test', bbRFC_SECRET, 0, False, False, True, token_ID,
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ for i in range(5):
+ hotp_code = gs(C.NK_get_hotp_code(1))
+ assert hotp_code != b''
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.otp
+def test_HOTP_counters(C):
+ """
+ # https://tools.ietf.org/html/rfc4226#page-32
+ """
+ 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
+ use_8_digits = True
+ HOTP_test_data = [
+ 1284755224, 1094287082, 137359152, 1726969429, 1640338314,
+ 868254676, 1918287922, 82162583, 673399871, 645520489,
+ ]
+ slot_number = 1
+ for counter, code in enumerate(HOTP_test_data):
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_hotp_slot(slot_number, b'python_test', bbRFC_SECRET, counter, use_8_digits, False, False, b'',
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ r = gs(C.NK_get_hotp_code(slot_number))
+ code = str(code)[-8:] if use_8_digits else str(code)[-6:]
+ assert bb(code) == r
+
+
+INT32_MAX = 2 ** 31 - 1
+@pytest.mark.otp
+def test_HOTP_64bit_counter(C):
+ if is_storage(C):
+ pytest.xfail('bug in NK Storage HOTP firmware - counter is set with a 8 digits string, '
+ 'however int32max takes 10 digits to be written')
+ oath = pytest.importorskip("oath")
+ lib_at = lambda t: bb(oath.hotp(RFC_SECRET, t, format='dec6'))
+ PIN_protection = False
+ use_8_digits = False
+ slot_number = 1
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection,
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ dev_res = []
+ lib_res = []
+ for t in range(INT32_MAX - 5, INT32_MAX + 5, 1):
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_hotp_slot(slot_number, b'python_test', bbRFC_SECRET, t, use_8_digits, False, False, b'',
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ code_device = gs(C.NK_get_hotp_code(slot_number))
+ dev_res += (t, code_device)
+ lib_res += (t, lib_at(t))
+ assert dev_res == lib_res
+
+
+@pytest.mark.otp
+def test_TOTP_64bit_time(C):
+ if is_storage(C):
+ pytest.xfail('bug in NK Storage TOTP firmware')
+ oath = pytest.importorskip("oath")
+ T = 1
+ lib_at = lambda t: bb(oath.totp(RFC_SECRET, t=t))
+ PIN_protection = False
+ slot_number = 1
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection,
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_totp_slot(slot_number, b'python_test', bbRFC_SECRET, 30, False, False, False, b'',
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ dev_res = []
+ lib_res = []
+ for t in range(INT32_MAX - 5, INT32_MAX + 5, 1):
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_totp_set_time(t) == DeviceErrorCode.STATUS_OK
+ code_device = gs((C.NK_get_totp_code(slot_number, T, 0, 30)))
+ dev_res += (t, code_device)
+ lib_res += (t, lib_at(t))
+ assert dev_res == lib_res
+
+
+@pytest.mark.otp
+@pytest.mark.xfail(reason="NK Pro: Test fails in 50% of cases due to test vectors set 1 second before interval count change"
+ "Here time is changed on seconds side only and miliseconds part is not being reset apparently"
+ "This results in available time to test of half a second on average, thus 50% failed cases"
+ "With disabled two first test vectors test passess 10/10 times"
+ "Fail may also occurs on NK Storage with lower occurrency since it needs less time to execute "
+ "commands")
+@pytest.mark.parametrize("PIN_protection", [False, True, ])
+def test_TOTP_RFC_usepin(C, PIN_protection):
+ slot_number = 1
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection,
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ # test according to https://tools.ietf.org/html/rfc6238#appendix-B
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_totp_slot(slot_number, b'python_test', bbRFC_SECRET, 30, True, False, False, b'',
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+
+ get_func = None
+ if PIN_protection:
+ get_func = lambda x, y, z, r: gs(C.NK_get_totp_code_PIN(x, y, z, r, DefaultPasswords.USER_TEMP))
+ else:
+ get_func = lambda x, y, z, r: gs(C.NK_get_totp_code(x, y, z, r))
+
+ # Mode: Sha1, time step X=30
+ test_data = [
+ #Time T (hex) TOTP
+ (59, 0x1, 94287082), # Warning - test vector time 1 second before interval count changes
+ (1111111109, 0x00000000023523EC, 7081804), # Warning - test vector time 1 second before interval count changes
+ (1111111111, 0x00000000023523ED, 14050471),
+ (1234567890, 0x000000000273EF07, 89005924),
+ (2000000000, 0x0000000003F940AA, 69279037),
+ # (20000000000, 0x0000000027BC86AA, 65353130), # 64bit is also checked in other test
+ ]
+ responses = []
+ data = []
+ correct = 0
+ for t, T, expected_code in test_data:
+ if PIN_protection:
+ C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP)
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_totp_set_time(t) == DeviceErrorCode.STATUS_OK
+ code_from_device = get_func(slot_number, T, 0, 30) # FIXME T is not changing the outcome
+ data += [ (t, bb(str(expected_code).zfill(8))) ]
+ responses += [ (t, code_from_device) ]
+ correct += expected_code == code_from_device
+ assert data == responses or correct == len(test_data)
+
+
+@pytest.mark.otp
+def test_get_slot_names(C):
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_erase_totp_slot(0, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ # erasing slot invalidates temporary password, so requesting authentication
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_erase_hotp_slot(0, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+
+ for i in range(15):
+ name = ffi.string(C.NK_get_totp_slot_name(i))
+ if name == '':
+ assert C.NK_get_last_command_status() == DeviceErrorCode.NOT_PROGRAMMED
+ for i in range(3):
+ name = ffi.string(C.NK_get_hotp_slot_name(i))
+ if name == '':
+ assert C.NK_get_last_command_status() == DeviceErrorCode.NOT_PROGRAMMED
+
+
+@pytest.mark.otp
+def test_get_OTP_codes(C):
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ for i in range(15):
+ code = gs(C.NK_get_totp_code(i, 0, 0, 0))
+ if code == b'':
+ assert C.NK_get_last_command_status() == DeviceErrorCode.NOT_PROGRAMMED
+
+ for i in range(3):
+ code = gs(C.NK_get_hotp_code(i))
+ if code == b'':
+ assert C.NK_get_last_command_status() == DeviceErrorCode.NOT_PROGRAMMED
+
+
+@pytest.mark.otp
+def test_get_OTP_code_from_not_programmed_slot(C):
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_erase_hotp_slot(0, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_erase_totp_slot(0, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+
+ code = gs(C.NK_get_hotp_code(0))
+ assert code == b''
+ assert C.NK_get_last_command_status() == DeviceErrorCode.NOT_PROGRAMMED
+
+ code = gs(C.NK_get_totp_code(0, 0, 0, 0))
+ assert code == b''
+ assert C.NK_get_last_command_status() == DeviceErrorCode.NOT_PROGRAMMED
+
+
+@pytest.mark.otp
+def test_get_code_user_authorize(C):
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_totp_slot(0, b'python_otp_auth', bbRFC_SECRET, 30, True, False, False, b'',
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ # enable PIN protection of OTP codes with write_config
+ # TODO create convinience function on C API side to enable/disable OTP USER_PIN protection
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, True, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ code = gs(C.NK_get_totp_code(0, 0, 0, 0))
+ assert code == b''
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_NOT_AUTHORIZED
+ # disable PIN protection with write_config
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ code = gs(C.NK_get_totp_code(0, 0, 0, 0))
+ assert code != b''
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+
+
+def cast_pointer_to_tuple(obj, typen, len):
+ # usage:
+ # config = cast_pointer_to_tuple(config_raw_data, 'uint8_t', 5)
+ return tuple(ffi.cast("%s [%d]" % (typen, len), obj)[0:len])
+
+
+def test_read_write_config(C):
+ # let's set sample config with pin protection and disabled scrolllock
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(0, 1, 2, True, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ config_raw_data = C.NK_read_config()
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+ config = cast_pointer_to_tuple(config_raw_data, 'uint8_t', 5)
+ assert config == (0, 1, 2, True, False)
+
+ # restore defaults and check
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ config_raw_data = C.NK_read_config()
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+ config = cast_pointer_to_tuple(config_raw_data, 'uint8_t', 5)
+ assert config == (255, 255, 255, False, True)
+
+
+@pytest.mark.lock_device
+@pytest.mark.factory_reset
+def test_factory_reset(C):
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_hotp_slot(1, b'python_test', bbRFC_SECRET, 0, False, False, False, b"",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert gs(C.NK_get_hotp_code(1)) == b"755224"
+ assert C.NK_factory_reset(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+ wait(10)
+ assert gs(C.NK_get_hotp_code(1)) != b"287082"
+ assert C.NK_get_last_command_status() == DeviceErrorCode.NOT_PROGRAMMED
+ # restore AES key
+ 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_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ if is_storage(C):
+ assert C.NK_clear_new_sd_card_warning(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.status
+def test_get_status(C):
+ status = C.NK_status()
+ s = gs(status)
+ assert len(s) > 0
+
+@pytest.mark.status
+def test_get_serial_number(C):
+ sn = C.NK_device_serial_number()
+ sn = gs(sn)
+ assert len(sn) > 0
+ print(('Serial number of the device: ', sn))
+
+
+@pytest.mark.otp
+@pytest.mark.parametrize("secret", ['000001', '00'*10+'ff', '00'*19+'ff', '000102',
+ '00'*29+'ff', '00'*39+'ff', '002EF43F51AFA97BA2B46418768123C9E1809A5B' ])
+def test_OTP_secret_started_from_null(C, secret):
+ """
+ NK Pro 0.8+, NK Storage 0.43+
+ """
+ skip_if_device_version_lower_than({'S': 43, 'P': 8})
+ if len(secret) > 40:
+ # feature: 320 bit long secret handling
+ skip_if_device_version_lower_than({'P': 8})
+
+ oath = pytest.importorskip("oath")
+ lib_at = lambda t: bb(oath.hotp(secret, t, format='dec6'))
+ PIN_protection = False
+ use_8_digits = False
+ slot_number = 1
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection,
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ dev_res = []
+ lib_res = []
+ for t in range(1,5):
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_hotp_slot(slot_number, b'null_secret', bb(secret), t, use_8_digits, False, False, b'',
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ code_device = gs(C.NK_get_hotp_code(slot_number))
+ dev_res += (t, code_device)
+ lib_res += (t, lib_at(t))
+ assert dev_res == lib_res
+
+
+@pytest.mark.otp
+@pytest.mark.parametrize("counter", [0, 3, 7, 0xffff,
+ 0xffffffff,
+ 0xffffffffffffffff] )
+def test_HOTP_slots_read_write_counter(C, counter):
+ """
+ Write different counters to all HOTP slots, read code and compare with 3rd party
+ :param counter:
+ """
+ if counter >= 1e7:
+ # Storage does not handle counters longer than 7 digits
+ skip_if_device_version_lower_than({'P': 7})
+
+ secret = RFC_SECRET
+ oath = pytest.importorskip("oath")
+ lib_at = lambda t: bb(oath.hotp(secret, t, format='dec6'))
+ PIN_protection = False
+ use_8_digits = False
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection,
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ dev_res = []
+ lib_res = []
+ for slot_number in range(3):
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_hotp_slot(slot_number, b'HOTP rw' + bytes(slot_number), bb(secret), counter, use_8_digits, False, False, b"",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ code_device = gs(C.NK_get_hotp_code(slot_number))
+ dev_res += (counter, code_device)
+ lib_res += (counter, lib_at(counter))
+ assert dev_res == lib_res
+
+
+@pytest.mark.otp
+@pytest.mark.parametrize("period", [30,60] )
+@pytest.mark.parametrize("time", range(21,70,20) )
+def test_TOTP_slots_read_write_at_time_period(C, time, period):
+ """
+ Write to all TOTP slots with specified period, read code at specified time
+ and compare with 3rd party
+ """
+ secret = RFC_SECRET
+ oath = pytest.importorskip("oath")
+ lib_at = lambda t: bb(oath.totp(RFC_SECRET, t=t, period=period))
+ PIN_protection = False
+ use_8_digits = False
+ T = 0
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection,
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ dev_res = []
+ lib_res = []
+ for slot_number in range(15):
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_totp_slot(slot_number, b'TOTP rw' + bytes(slot_number), bb(secret), period, use_8_digits, False, False, b"",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_totp_set_time(time) == DeviceErrorCode.STATUS_OK
+ code_device = gs(C.NK_get_totp_code(slot_number, T, 0, period))
+ dev_res += (time, code_device)
+ lib_res += (time, lib_at(time))
+ assert dev_res == lib_res
+
+
+@pytest.mark.otp
+@pytest.mark.parametrize("secret", [RFC_SECRET, 2*RFC_SECRET, '12'*10, '12'*30] )
+def test_TOTP_secrets(C, secret):
+ '''
+ NK Pro 0.8+, NK Storage 0.44+
+ '''
+ skip_if_device_version_lower_than({'S': 44, 'P': 8})
+
+ if len(secret)>20*2: #*2 since secret is in hex
+ # pytest.skip("Secret lengths over 20 bytes are not supported by NK Pro 0.7 and NK Storage")
+ skip_if_device_version_lower_than({'P': 8})
+ slot_number = 0
+ time = 0
+ period = 30
+ oath = pytest.importorskip("oath")
+ lib_at = lambda t: bb(oath.totp(secret, t=t, period=period))
+ PIN_protection = False
+ use_8_digits = False
+ T = 0
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection,
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ dev_res = []
+ lib_res = []
+ assert C.NK_write_totp_slot(slot_number, b'secret' + bytes(len(secret)), bb(secret), period, use_8_digits, False, False, b"",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_totp_set_time(time) == DeviceErrorCode.STATUS_OK
+ code_device = gs(C.NK_get_totp_code(slot_number, T, 0, period))
+ dev_res += (time, code_device)
+ lib_res += (time, lib_at(time))
+ assert dev_res == lib_res
+
+
+@pytest.mark.otp
+@pytest.mark.parametrize("secret", [RFC_SECRET, 2*RFC_SECRET, '12'*10, '12'*30] )
+def test_HOTP_secrets(C, secret):
+ """
+ NK Pro 0.8+
+ feature needed: support for 320bit secrets
+ """
+ if len(secret)>40:
+ skip_if_device_version_lower_than({'P': 8})
+
+ slot_number = 0
+ counter = 0
+ oath = pytest.importorskip("oath")
+ lib_at = lambda t: bb(oath.hotp(secret, counter=t))
+ PIN_protection = False
+ use_8_digits = False
+ T = 0
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection,
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ dev_res = []
+ lib_res = []
+ # repeat authentication for Pro 0.7
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_hotp_slot(slot_number, b'secret' + bytes(len(secret)), bb(secret), counter, use_8_digits, False, False, b"",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ code_device = gs(C.NK_get_hotp_code(slot_number))
+ dev_res += (counter, code_device)
+ lib_res += (counter, lib_at(counter))
+ assert dev_res == lib_res
+
+
+def test_special_double_press(C):
+ """
+ requires manual check after function run
+ double press each of num-, scroll-, caps-lock and check inserted OTP codes (each 1st should be 755224)
+ on nkpro 0.7 scrolllock should do nothing, on nkpro 0.8+ should return OTP code
+ """
+ secret = RFC_SECRET
+ counter = 0
+ PIN_protection = False
+ use_8_digits = False
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(0, 1, 2, PIN_protection, not PIN_protection,
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ for slot_number in range(3):
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_hotp_slot(slot_number, b'double' + bytes(slot_number), bb(secret), counter, use_8_digits, False, False, b"",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ # requires manual check
+
+
+@pytest.mark.otp
+def test_edit_OTP_slot(C):
+ """
+ should change slots counter and name without changing its secret (using null secret for second update)
+ """
+ # counter is not getting updated under Storage v0.43 - #TOREPORT
+ skip_if_device_version_lower_than({'S': 44, 'P': 7})
+
+ secret = RFC_SECRET
+ counter = 0
+ PIN_protection = False
+ use_8_digits = False
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection,
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ slot_number = 0
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ first_name = b'edit slot'
+ assert C.NK_write_hotp_slot(slot_number, first_name, bb(secret), counter, use_8_digits, False, False, b"",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert gs(C.NK_get_hotp_slot_name(slot_number)) == first_name
+
+
+ first_code = gs(C.NK_get_hotp_code(slot_number))
+ changed_name = b'changedname'
+ empty_secret = b''
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_hotp_slot(slot_number, changed_name, empty_secret, counter, use_8_digits, False, False, b"",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ second_code = gs(C.NK_get_hotp_code(slot_number))
+ assert first_code == second_code
+ assert gs(C.NK_get_hotp_slot_name(slot_number)) == changed_name
+
+
+@pytest.mark.otp
+@pytest.mark.skip
+@pytest.mark.parametrize("secret", ['31323334353637383930'*2,'31323334353637383930'*4] )
+def test_TOTP_codes_from_nitrokeyapp(secret, C):
+ """
+ Helper test for manual TOTP check of written secret by Nitrokey App
+ Destined to run by hand
+ """
+ slot_number = 0
+ PIN_protection = False
+ period = 30
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection,
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ code_device = gs(C.NK_get_totp_code(slot_number, 0, 0, period))
+
+ oath = pytest.importorskip("oath")
+ lib_at = lambda : bb(oath.totp(secret, period=period))
+ print (lib_at())
+ assert lib_at() == code_device
diff --git a/unittest/test_storage.py b/unittest/test_storage.py
new file mode 100644
index 0000000..2466779
--- /dev/null
+++ b/unittest/test_storage.py
@@ -0,0 +1,304 @@
+import pprint
+import pytest
+
+from conftest import skip_if_device_version_lower_than
+from constants import DefaultPasswords, DeviceErrorCode, bb
+from misc import gs, wait
+pprint = pprint.PrettyPrinter(indent=4).pprint
+
+
+def get_dict_from_dissect(status):
+ x = []
+ for s in status.split('\n'):
+ try:
+ if not ':' in s: continue
+ ss = s.replace('\t', '').replace(' (int) ', '').split(':')
+ if not len(ss) == 2: continue
+ x.append(ss)
+ except:
+ pass
+ d = {k.strip(): v.strip() for k, v in x}
+ return d
+
+
+@pytest.mark.other
+@pytest.mark.info
+def test_get_status_storage(C):
+ skip_if_device_version_lower_than({'S': 43})
+ 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'))
+ default_admin_password_retry_count = 3
+ assert int(status_dict['AdminPwRetryCount']) == default_admin_password_retry_count
+
+
+@pytest.mark.other
+@pytest.mark.info
+def test_sd_card_usage(C):
+ skip_if_device_version_lower_than({'S': 43})
+ data_pointer = C.NK_get_SD_usage_data_as_string()
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+ data_string = gs(data_pointer)
+ assert len(data_string) > 0
+ data_dict = get_dict_from_dissect(data_string.decode("ascii"))
+ assert int(data_dict['WriteLevelMax']) <= 100
+
+
+@pytest.mark.encrypted
+def test_encrypted_volume_unlock(C):
+ skip_if_device_version_lower_than({'S': 43})
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.hidden
+def test_encrypted_volume_unlock_hidden(C):
+ skip_if_device_version_lower_than({'S': 43})
+ hidden_volume_password = b'hiddenpassword'
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ assert C.NK_create_hidden_volume(0, 20, 21, hidden_volume_password) == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_hidden_volume(hidden_volume_password) == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.hidden
+def test_encrypted_volume_setup_multiple_hidden_lock(C):
+ import random
+ skip_if_device_version_lower_than({'S': 45}) #hangs device on lower version
+ hidden_volume_password = b'hiddenpassword' + bb(str(random.randint(0,100)))
+ p = lambda i: hidden_volume_password + bb(str(i))
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ for i in range(4):
+ assert C.NK_create_hidden_volume(i, 20+i*10, 20+i*10+i+1, p(i) ) == DeviceErrorCode.STATUS_OK
+ for i in range(4):
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_hidden_volume(p(i)) == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.hidden
+@pytest.mark.parametrize("volumes_to_setup", range(1, 5))
+def test_encrypted_volume_setup_multiple_hidden_no_lock_device_volumes(C, volumes_to_setup):
+ skip_if_device_version_lower_than({'S': 43})
+ hidden_volume_password = b'hiddenpassword'
+ p = lambda i: hidden_volume_password + bb(str(i))
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ for i in range(volumes_to_setup):
+ assert C.NK_create_hidden_volume(i, 20+i*10, 20+i*10+i+1, p(i)) == DeviceErrorCode.STATUS_OK
+
+ assert C.NK_lock_encrypted_volume() == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+
+ for i in range(volumes_to_setup):
+ assert C.NK_unlock_hidden_volume(p(i)) == DeviceErrorCode.STATUS_OK
+ # TODO mount and test for files
+ assert C.NK_lock_hidden_volume() == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.hidden
+@pytest.mark.parametrize("volumes_to_setup", range(1, 5))
+def test_encrypted_volume_setup_multiple_hidden_no_lock_device_volumes_unlock_at_once(C, volumes_to_setup):
+ skip_if_device_version_lower_than({'S': 43})
+ hidden_volume_password = b'hiddenpassword'
+ p = lambda i: hidden_volume_password + bb(str(i))
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ for i in range(volumes_to_setup):
+ assert C.NK_create_hidden_volume(i, 20+i*10, 20+i*10+i+1, p(i)) == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_hidden_volume(p(i)) == DeviceErrorCode.STATUS_OK
+ assert C.NK_lock_hidden_volume() == DeviceErrorCode.STATUS_OK
+
+ assert C.NK_lock_encrypted_volume() == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+
+ for i in range(volumes_to_setup):
+ assert C.NK_unlock_hidden_volume(p(i)) == DeviceErrorCode.STATUS_OK
+ # TODO mount and test for files
+ assert C.NK_lock_hidden_volume() == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.hidden
+@pytest.mark.parametrize("use_slot", range(4))
+def test_encrypted_volume_setup_one_hidden_no_lock_device_slot(C, use_slot):
+ skip_if_device_version_lower_than({'S': 43})
+ hidden_volume_password = b'hiddenpassword'
+ p = lambda i: hidden_volume_password + bb(str(i))
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ i = use_slot
+ assert C.NK_create_hidden_volume(i, 20+i*10, 20+i*10+i+1, p(i)) == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_hidden_volume(p(i)) == DeviceErrorCode.STATUS_OK
+ assert C.NK_lock_hidden_volume() == DeviceErrorCode.STATUS_OK
+
+ assert C.NK_lock_encrypted_volume() == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+
+ for j in range(3):
+ assert C.NK_unlock_hidden_volume(p(i)) == DeviceErrorCode.STATUS_OK
+ # TODO mount and test for files
+ assert C.NK_lock_hidden_volume() == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.hidden
+@pytest.mark.PWS
+def test_password_safe_slot_name_corruption(C):
+ skip_if_device_version_lower_than({'S': 43})
+ volumes_to_setup = 4
+ # connected with encrypted volumes, possible also with hidden
+ def fill(s, wid):
+ assert wid >= len(s)
+ numbers = '1234567890' * 4
+ s += numbers[:wid - len(s)]
+ assert len(s) == wid
+ return bb(s)
+
+ def get_pass(suffix):
+ return fill('pass' + suffix, 20)
+
+ def get_loginname(suffix):
+ return fill('login' + suffix, 32)
+
+ def get_slotname(suffix):
+ return fill('slotname' + suffix, 11)
+
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ PWS_slot_count = 16
+ for i in range(0, PWS_slot_count):
+ iss = str(i)
+ assert C.NK_write_password_safe_slot(i,
+ get_slotname(iss), get_loginname(iss),
+ get_pass(iss)) == DeviceErrorCode.STATUS_OK
+
+ def check_PWS_correctness(C):
+ for i in range(0, PWS_slot_count):
+ iss = str(i)
+ assert gs(C.NK_get_password_safe_slot_name(i)) == get_slotname(iss)
+ assert gs(C.NK_get_password_safe_slot_login(i)) == get_loginname(iss)
+ assert gs(C.NK_get_password_safe_slot_password(i)) == get_pass(iss)
+
+ hidden_volume_password = b'hiddenpassword'
+ p = lambda i: hidden_volume_password + bb(str(i))
+ def check_volumes_correctness(C):
+ for i in range(volumes_to_setup):
+ assert C.NK_unlock_hidden_volume(p(i)) == DeviceErrorCode.STATUS_OK
+ # TODO mount and test for files
+ assert C.NK_lock_hidden_volume() == DeviceErrorCode.STATUS_OK
+
+ check_PWS_correctness(C)
+
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ for i in range(volumes_to_setup):
+ assert C.NK_create_hidden_volume(i, 20+i*10, 20+i*10+i+1, p(i)) == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_hidden_volume(p(i)) == DeviceErrorCode.STATUS_OK
+ assert C.NK_lock_hidden_volume() == DeviceErrorCode.STATUS_OK
+
+ assert C.NK_lock_encrypted_volume() == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+
+ check_volumes_correctness(C)
+ check_PWS_correctness(C)
+ check_volumes_correctness(C)
+ check_PWS_correctness(C)
+
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ check_volumes_correctness(C)
+ check_PWS_correctness(C)
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ check_volumes_correctness(C)
+ check_PWS_correctness(C)
+
+
+@pytest.mark.hidden
+def test_hidden_volume_corruption(C):
+ # bug: this should return error without unlocking encrypted volume each hidden volume lock, but it does not
+ skip_if_device_version_lower_than({'S': 43})
+ hidden_volume_password = b'hiddenpassword'
+ p = lambda i: hidden_volume_password + bb(str(i))
+ volumes_to_setup = 4
+ for i in range(volumes_to_setup):
+ assert C.NK_create_hidden_volume(i, 20 + i * 10, 20 + i * 10 + i + 1, p(i)) == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_hidden_volume(p(i)) == DeviceErrorCode.STATUS_OK
+ assert C.NK_lock_hidden_volume() == DeviceErrorCode.STATUS_OK
+
+ assert C.NK_lock_encrypted_volume() == DeviceErrorCode.STATUS_OK
+
+ assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ for i in range(volumes_to_setup):
+ assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ assert C.NK_unlock_hidden_volume(p(i)) == DeviceErrorCode.STATUS_OK
+ wait(2)
+ assert C.NK_lock_hidden_volume() == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.unencrypted
+def test_unencrypted_volume_set_read_only(C):
+ skip_if_device_version_lower_than({'S': 43})
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_set_unencrypted_read_only(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.unencrypted
+def test_unencrypted_volume_set_read_write(C):
+ skip_if_device_version_lower_than({'S': 43})
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_set_unencrypted_read_write(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.other
+def test_export_firmware(C):
+ skip_if_device_version_lower_than({'S': 43})
+ assert C.NK_export_firmware(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.other
+def test_clear_new_sd_card_notification(C):
+ skip_if_device_version_lower_than({'S': 43})
+ assert C.NK_clear_new_sd_card_warning(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.encrypted
+@pytest.mark.slowtest
+@pytest.mark.skip(reason='long test (about 1h)')
+def test_fill_SD_card(C):
+ skip_if_device_version_lower_than({'S': 43})
+ status = C.NK_fill_SD_card_with_random_data(DefaultPasswords.ADMIN)
+ assert status == DeviceErrorCode.STATUS_OK or status == DeviceErrorCode.BUSY
+ while 1:
+ value = C.NK_get_progress_bar_value()
+ if value == -1: break
+ assert 0 <= value <= 100
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+ wait(5)
+
+
+@pytest.mark.other
+@pytest.mark.info
+def test_get_busy_progress_on_idle(C):
+ skip_if_device_version_lower_than({'S': 43})
+ value = C.NK_get_progress_bar_value()
+ assert value == -1
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.update
+def test_change_update_password(C):
+ skip_if_device_version_lower_than({'S': 43})
+ 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_change_update_password(DefaultPasswords.UPDATE_TEMP, DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.other
+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