From 130d7f12e42505a33f41073983d868ca0c3c78d1 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Tue, 8 Nov 2016 17:03:48 +0100 Subject: Fix for auth issue in NK Pro for commands EraseSlot, WriteToSlot, GetCode + tests Signed-off-by: Szczepan Zalega --- CMakeLists.txt | 2 + include/command_id.h | 1 + include/stick10_commands.h | 4 + include/stick10_commands_0.8.h | 178 +++++++++++++++++++++++++++++++++++++++++ unittest/test3.cc | 78 ++++++++++++++++++ 5 files changed, 263 insertions(+) create mode 100644 include/stick10_commands_0.8.h create mode 100644 unittest/test3.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index e9cd54f..c324067 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,9 @@ set(SOURCE_FILES unittest/test_C_API.cpp unittest/catch_main.cpp unittest/test2.cc + unittest/test3.cc include/LongOperationInProgressException.h + include/stick10_commands_0.8.h ) add_executable(libnitrokey ${SOURCE_FILES}) \ No newline at end of file diff --git a/include/command_id.h b/include/command_id.h index a3806f0..1f7affc 100644 --- a/include/command_id.h +++ b/include/command_id.h @@ -65,6 +65,7 @@ enum class CommandID : uint8_t { FACTORY_RESET = 0x13, CHANGE_USER_PIN = 0x14, CHANGE_ADMIN_PIN = 0x15, + WRITE_TO_SLOT_2 = 0x16, ENABLE_CRYPTED_PARI = 0x20, DISABLE_CRYPTED_PARI = 0x20 + 1, //@unused diff --git a/include/stick10_commands.h b/include/stick10_commands.h index f02fd70..5ae2591 100644 --- a/include/stick10_commands.h +++ b/include/stick10_commands.h @@ -48,6 +48,7 @@ class EraseSlot : Command { public: struct CommandPayload { uint8_t slot_number; + uint8_t temporary_admin_password[25]; bool isValid() const { return !(slot_number & 0xF0); } std::string dissect() const { @@ -137,6 +138,7 @@ class WriteToHOTPSlot : Command { }; class WriteToTOTPSlot : Command { + //admin auth public: struct CommandPayload { uint8_t slot_number; @@ -182,6 +184,7 @@ class WriteToTOTPSlot : Command { }; class GetTOTP : Command { + //user auth public: struct CommandPayload { uint8_t slot_number; @@ -612,6 +615,7 @@ class PasswordSafeSendSlotViaHID : Command { // TODO "Device::passwordSafeSendSlotDataViaHID" class WriteGeneralConfig : Command { + //admin auth public: struct CommandPayload { union{ diff --git a/include/stick10_commands_0.8.h b/include/stick10_commands_0.8.h new file mode 100644 index 0000000..037a777 --- /dev/null +++ b/include/stick10_commands_0.8.h @@ -0,0 +1,178 @@ +// +// Created by sz on 08.11.16. +// + +#ifndef LIBNITROKEY_STICK10_COMMANDS_0_8_H +#define LIBNITROKEY_STICK10_COMMANDS_0_8_H + +#include +#include +#include +#include +#include "inttypes.h" +#include "command.h" +#include "device_proto.h" + +namespace nitrokey { + namespace proto { + +/* + * Stick10 protocol definition + */ + namespace stick10_08 { + + class EraseSlot : Command { + 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; + return ss.str(); + } + } __packed; + + typedef Transaction + CommandTransaction; + }; + + class WriteToHOTPSlot : Command { + //admin auth + public: + struct CommandPayload { + uint8_t temporary_admin_password[25]; + 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; + }; + + bool isValid() const { return true; } + + std::string dissect() const { + std::stringstream ss; + ss << "temporary_admin_password:\t" << temporary_admin_password << std::endl; + ss << "slot_secret:" << std::endl + << ::nitrokey::misc::hexdump((const char *) (&slot_secret), sizeof 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; + + return ss.str(); + } + } __packed; + + typedef Transaction + CommandTransaction; + }; + + class WriteToHOTPSlot_2 : Command { + public: + struct CommandPayload { + uint8_t temporary_admin_password[25]; + uint8_t slot_number; + uint8_t slot_name[15]; + 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 << "temporary_admin_password:\t" << temporary_admin_password << std::endl; + ss << "slot_number:\t" << (int) (slot_number) << std::endl; + ss << "slot_name:\t" << slot_name << std::endl; + ss << "slot_counter:\t[" << (int) slot_counter << "]\t" + << ::nitrokey::misc::hexdump((const char *) (&slot_counter), sizeof slot_counter, false); + + return ss.str(); + } + } __packed; + + typedef Transaction + CommandTransaction; + }; + + + class GetTOTP : Command { + //user auth + public: + struct CommandPayload { + uint8_t slot_number; + uint64_t challenge; + uint64_t last_totp_time; + uint8_t last_interval; + uint8_t user_temporary_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; + 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 + CommandTransaction; + }; + + + } + } +} +#endif //LIBNITROKEY_STICK10_COMMANDS_0_8_H diff --git a/unittest/test3.cc b/unittest/test3.cc new file mode 100644 index 0000000..6fab862 --- /dev/null +++ b/unittest/test3.cc @@ -0,0 +1,78 @@ +// +// Created by sz on 08.11.16. +// + +#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() + +static const char *const default_admin_pin = "12345678"; +static const char *const default_user_pin = "123456"; +const char * temporary_password = "123456789012345678901234"; +const char * RFC_SECRET = "12345678901234567890"; + +#include "catch.hpp" + +#include +#include +#include +#include "device_proto.h" +#include "log.h" +#include "stick10_commands.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::proto::stick10; +using namespace nitrokey::log; +using namespace nitrokey::misc; + +void connect_and_setup(Stick10 &stick) { + bool connected = stick.connect(); + REQUIRE(connected == true); + Log::instance().set_loglevel(Loglevel::DEBUG); +} + +void authorize(Stick10 &stick) { + auto authreq = get_payload(); + strcpy((char *) (authreq.card_password), default_admin_pin); + strcpy((char *) (authreq.temporary_password), temporary_password); + FirstAuthenticate::CommandTransaction::run(stick, authreq); +} + +TEST_CASE("write slot", "[pronew]"){ + Stick10 stick; + connect_and_setup(stick); + + auto p = get_payload(); +// p.slot_number = 0 + 0x10; + strcpyT(p.slot_secret, RFC_SECRET); + strcpyT(p.temporary_admin_password, temporary_password); + p.use_8_digits = true; + stick10_08::WriteToHOTPSlot::CommandTransaction::run(stick, p); + + auto p2 = get_payload(); + strcpyT(p2.temporary_admin_password, temporary_password); + p2.slot_number = 0 + 0x10; + p2.slot_counter = 0; + strcpyT(p2.slot_name, "test name aaa"); + stick10_08::WriteToHOTPSlot_2::CommandTransaction::run(stick, p2); + + auto p3 = get_payload(); + p3.slot_number = 0 + 0x10; + GetHOTP::CommandTransaction::run(stick, p3); + +} + + +TEST_CASE("erase slot", "[pronew]"){ + Stick10 stick; + connect_and_setup(stick); + authorize(stick); + + auto erase_payload = get_payload(); + erase_payload.slot_number = 1 + 0x10; + strcpyT(erase_payload.temporary_admin_password, temporary_password); + stick10_08::EraseSlot::CommandTransaction::run(stick, erase_payload); +} \ No newline at end of file -- cgit v1.2.1 From 119ea0b111c9ca46fda32911c1c8c33b36aad3db Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Tue, 8 Nov 2016 18:58:17 +0100 Subject: Authorization fix: GetHOTP and WriteGeneralConfig + test Signed-off-by: Szczepan Zalega --- command_id.cc | 3 ++ include/stick10_commands_0.8.h | 83 +++++++++++++++++++++++++++++++++++++++++- unittest/test3.cc | 69 ++++++++++++++++++++++++++++++++--- 3 files changed, 149 insertions(+), 6 deletions(-) diff --git a/command_id.cc b/command_id.cc index 9512b7d..a93d05c 100644 --- a/command_id.cc +++ b/command_id.cc @@ -139,6 +139,9 @@ const char *commandid_to_string(CommandID id) { 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; } return "UNKNOWN"; } diff --git a/include/stick10_commands_0.8.h b/include/stick10_commands_0.8.h index 037a777..0c6633c 100644 --- a/include/stick10_commands_0.8.h +++ b/include/stick10_commands_0.8.h @@ -12,6 +12,7 @@ #include "inttypes.h" #include "command.h" #include "device_proto.h" +#include "stick10_commands.h" namespace nitrokey { namespace proto { @@ -20,6 +21,8 @@ namespace nitrokey { * Stick10 protocol definition */ namespace stick10_08 { + using stick10::FirstAuthenticate; + using stick10::UserAuthenticate; class EraseSlot : Command { public: @@ -117,20 +120,68 @@ namespace nitrokey { CommandTransaction; }; + class GetHOTP : Command { + public: + struct CommandPayload { + uint8_t temporary_user_password[25]; + uint8_t slot_number; + + bool isValid() const { return (slot_number & 0xF0); } + std::string dissect() const { + std::stringstream ss; + ss << "temporary_user_password:\t" << temporary_user_password << std::endl; + 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 + CommandTransaction; + }; + class GetTOTP : Command { //user auth public: struct CommandPayload { + uint8_t temporary_user_password[25]; uint8_t slot_number; uint64_t challenge; uint64_t last_totp_time; uint8_t last_interval; - uint8_t user_temporary_password[25]; bool isValid() const { return !(slot_number & 0xF0); } std::string dissect() const { std::stringstream ss; + ss << "temporary_user_password:\t" << temporary_user_password << std::endl; 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; @@ -172,6 +223,36 @@ namespace nitrokey { }; + class WriteGeneralConfig : Command { + //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 + CommandTransaction; + }; } } } diff --git a/unittest/test3.cc b/unittest/test3.cc index 6fab862..226e35c 100644 --- a/unittest/test3.cc +++ b/unittest/test3.cc @@ -16,15 +16,13 @@ const char * RFC_SECRET = "12345678901234567890"; #include #include "device_proto.h" #include "log.h" -#include "stick10_commands.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::proto::stick10; +using namespace nitrokey::proto::stick10_08; using namespace nitrokey::log; using namespace nitrokey::misc; @@ -39,6 +37,11 @@ void authorize(Stick10 &stick) { 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(); + 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]"){ @@ -46,7 +49,6 @@ TEST_CASE("write slot", "[pronew]"){ connect_and_setup(stick); auto p = get_payload(); -// p.slot_number = 0 + 0x10; strcpyT(p.slot_secret, RFC_SECRET); strcpyT(p.temporary_admin_password, temporary_password); p.use_8_digits = true; @@ -71,8 +73,65 @@ TEST_CASE("erase slot", "[pronew]"){ connect_and_setup(stick); authorize(stick); + auto p3 = get_payload(); + p3.slot_number = 0 + 0x10; + GetHOTP::CommandTransaction::run(stick, p3); + auto erase_payload = get_payload(); - erase_payload.slot_number = 1 + 0x10; + erase_payload.slot_number = 0 + 0x10; strcpyT(erase_payload.temporary_admin_password, temporary_password); stick10_08::EraseSlot::CommandTransaction::run(stick, erase_payload); + + auto p4 = get_payload(); + p4.slot_number = 0 + 0x10; + REQUIRE_THROWS( + GetHOTP::CommandTransaction::run(stick, p4) + ); +} + +TEST_CASE("write general config", "[pronew]") { + Stick10 stick; + connect_and_setup(stick); + authorize(stick); + + auto p = get_payload(); + 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 OTP", "[pronew]") { + Stick10 stick; + connect_and_setup(stick); + authorize(stick); + + auto p = get_payload(); + p.enable_user_password = 1; + strcpyT(p.temporary_admin_password, temporary_password); + WriteGeneralConfig::CommandTransaction::run(stick, p); + + auto pw = get_payload(); + strcpyT(pw.slot_secret, RFC_SECRET); + strcpyT(pw.temporary_admin_password, temporary_password); + pw.use_8_digits = true; + WriteToHOTPSlot::CommandTransaction::run(stick, pw); + + auto pw2 = get_payload(); + strcpyT(pw2.temporary_admin_password, temporary_password); + pw2.slot_number = 0 + 0x10; + pw2.slot_counter = 0; + strcpyT(pw2.slot_name, "test name aaa"); + WriteToHOTPSlot_2::CommandTransaction::run(stick, pw2); + + auto p3 = get_payload(); + p3.slot_number = 0 + 0x10; + REQUIRE_THROWS( + GetHOTP::CommandTransaction::run(stick, p3); + ); + strcpyT(p3.temporary_user_password, temporary_password); + GetHOTP::CommandTransaction::run(stick, p3); + } \ No newline at end of file -- cgit v1.2.1 From f76ac655fff3df7eb0e645ca39d18510714b0039 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Wed, 9 Nov 2016 14:29:18 +0100 Subject: Authorization fix: GetTOTP and WriteToTOTPSLot + test Signed-off-by: Szczepan Zalega --- include/stick10_commands_0.8.h | 77 ++++++++++++++++++++++++++++++++++++++++++ unittest/test3.cc | 46 +++++++++++++++++++++++-- 2 files changed, 121 insertions(+), 2 deletions(-) diff --git a/include/stick10_commands_0.8.h b/include/stick10_commands_0.8.h index 0c6633c..cee0d78 100644 --- a/include/stick10_commands_0.8.h +++ b/include/stick10_commands_0.8.h @@ -23,6 +23,7 @@ namespace nitrokey { namespace stick10_08 { using stick10::FirstAuthenticate; using stick10::UserAuthenticate; + using stick10::SetTime; class EraseSlot : Command { public: @@ -120,6 +121,82 @@ namespace nitrokey { CommandTransaction; }; + + class WriteToTOTPSlot : Command { + //admin auth + public: + struct CommandPayload { + uint8_t temporary_admin_password[25]; + 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; + }; + + bool isValid() const { return true; } + + std::string dissect() const { + std::stringstream ss; + ss << "temporary_admin_password:\t" << temporary_admin_password << std::endl; + ss << "slot_secret:" << std::endl + << ::nitrokey::misc::hexdump((const char *) (&slot_secret), sizeof 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; + + return ss.str(); + } + } __packed; + + typedef Transaction + CommandTransaction; + }; + + class WriteToTOTPSlot_2 : Command { + public: + struct CommandPayload { + uint8_t temporary_admin_password[25]; + uint8_t slot_number; + uint8_t slot_name[15]; + uint16_t slot_interval; + + bool isValid() const { return !(slot_number & 0xF0); } + + std::string dissect() const { + std::stringstream ss; + ss << "temporary_admin_password:\t" << temporary_admin_password << std::endl; + ss << "slot_number:\t" << (int) (slot_number) << std::endl; + ss << "slot_name:\t" << slot_name << std::endl; + ss << "slot_interval:\t" << (int)slot_interval << std::endl; + + return ss.str(); + } + } __packed; + + typedef Transaction + CommandTransaction; + }; + + class GetHOTP : Command { public: struct CommandPayload { diff --git a/unittest/test3.cc b/unittest/test3.cc index 226e35c..8a9423f 100644 --- a/unittest/test3.cc +++ b/unittest/test3.cc @@ -103,7 +103,7 @@ TEST_CASE("write general config", "[pronew]") { WriteGeneralConfig::CommandTransaction::run(stick, p); } -TEST_CASE("authorize user OTP", "[pronew]") { +TEST_CASE("authorize user HOTP", "[pronew]") { Stick10 stick; connect_and_setup(stick); authorize(stick); @@ -132,6 +132,48 @@ TEST_CASE("authorize user OTP", "[pronew]") { GetHOTP::CommandTransaction::run(stick, p3); ); strcpyT(p3.temporary_user_password, temporary_password); - GetHOTP::CommandTransaction::run(stick, p3); + auto code_response = GetHOTP::CommandTransaction::run(stick, p3); + REQUIRE(code_response.data().code == 1284755224); + +} + + +TEST_CASE("authorize user TOTP", "[pronew]") { + Stick10 stick; + connect_and_setup(stick); + authorize(stick); + + auto p = get_payload(); + p.enable_user_password = 1; + strcpyT(p.temporary_admin_password, temporary_password); + WriteGeneralConfig::CommandTransaction::run(stick, p); + + auto pw = get_payload(); + strcpyT(pw.slot_secret, RFC_SECRET); + strcpyT(pw.temporary_admin_password, temporary_password); + pw.use_8_digits = true; + WriteToTOTPSlot::CommandTransaction::run(stick, pw); + + auto pw2 = get_payload(); + strcpyT(pw2.temporary_admin_password, temporary_password); + pw2.slot_number = 0 + 0x20; + pw2.slot_interval= 30; + strcpyT(pw2.slot_name, "test name TOTP"); + WriteToTOTPSlot_2::CommandTransaction::run(stick, pw2); + + auto p_get_totp = get_payload(); + 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(); + 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); } \ No newline at end of file -- cgit v1.2.1 From e647b0cf5def2c76958968ddad8f7808d966aa49 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Wed, 9 Nov 2016 14:55:48 +0100 Subject: Tests - ensure required env is set, fix HOTP const Signed-off-by: Szczepan Zalega --- unittest/test3.cc | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/unittest/test3.cc b/unittest/test3.cc index 8a9423f..6395cb6 100644 --- a/unittest/test3.cc +++ b/unittest/test3.cc @@ -48,19 +48,24 @@ TEST_CASE("write slot", "[pronew]"){ Stick10 stick; connect_and_setup(stick); - auto p = get_payload(); + auto p = get_payload(); strcpyT(p.slot_secret, RFC_SECRET); strcpyT(p.temporary_admin_password, temporary_password); p.use_8_digits = true; stick10_08::WriteToHOTPSlot::CommandTransaction::run(stick, p); - auto p2 = get_payload(); + auto p2 = get_payload(); strcpyT(p2.temporary_admin_password, temporary_password); p2.slot_number = 0 + 0x10; p2.slot_counter = 0; strcpyT(p2.slot_name, "test name aaa"); stick10_08::WriteToHOTPSlot_2::CommandTransaction::run(stick, p2); + auto pc = get_payload(); + pc.enable_user_password = 0; + strcpyT(pc.temporary_admin_password, temporary_password); + WriteGeneralConfig::CommandTransaction::run(stick, pc); + auto p3 = get_payload(); p3.slot_number = 0 + 0x10; GetHOTP::CommandTransaction::run(stick, p3); @@ -73,14 +78,19 @@ TEST_CASE("erase slot", "[pronew]"){ connect_and_setup(stick); authorize(stick); + auto p = get_payload(); + p.enable_user_password = 0; + strcpyT(p.temporary_admin_password, temporary_password); + WriteGeneralConfig::CommandTransaction::run(stick, p); + auto p3 = get_payload(); p3.slot_number = 0 + 0x10; GetHOTP::CommandTransaction::run(stick, p3); - auto erase_payload = get_payload(); + auto erase_payload = get_payload(); erase_payload.slot_number = 0 + 0x10; strcpyT(erase_payload.temporary_admin_password, temporary_password); - stick10_08::EraseSlot::CommandTransaction::run(stick, erase_payload); + EraseSlot::CommandTransaction::run(stick, erase_payload); auto p4 = get_payload(); p4.slot_number = 0 + 0x10; @@ -133,7 +143,7 @@ TEST_CASE("authorize user HOTP", "[pronew]") { ); strcpyT(p3.temporary_user_password, temporary_password); auto code_response = GetHOTP::CommandTransaction::run(stick, p3); - REQUIRE(code_response.data().code == 1284755224); + REQUIRE(code_response.data().code == 84755224); } -- cgit v1.2.1 From 17a1961704791c61fd69889867b3285c37539b59 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Wed, 9 Nov 2016 18:25:36 +0100 Subject: Authorize before write in WriteToSlot test Signed-off-by: Szczepan Zalega --- unittest/test3.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/unittest/test3.cc b/unittest/test3.cc index 6395cb6..bd6f2a5 100644 --- a/unittest/test3.cc +++ b/unittest/test3.cc @@ -47,6 +47,7 @@ void authorize(Stick10 &stick) { TEST_CASE("write slot", "[pronew]"){ Stick10 stick; connect_and_setup(stick); + authorize(stick); auto p = get_payload(); strcpyT(p.slot_secret, RFC_SECRET); -- cgit v1.2.1 From 90bf7f564c50bf48799056179dbc5a09b7782d27 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Wed, 9 Nov 2016 18:30:07 +0100 Subject: Check firware version in Pro 0.8 test Signed-off-by: Szczepan Zalega --- include/stick10_commands_0.8.h | 1 + unittest/test3.cc | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/include/stick10_commands_0.8.h b/include/stick10_commands_0.8.h index cee0d78..9099b3d 100644 --- a/include/stick10_commands_0.8.h +++ b/include/stick10_commands_0.8.h @@ -24,6 +24,7 @@ namespace nitrokey { using stick10::FirstAuthenticate; using stick10::UserAuthenticate; using stick10::SetTime; + using stick10::GetStatus; class EraseSlot : Command { public: diff --git a/unittest/test3.cc b/unittest/test3.cc index bd6f2a5..7f779a5 100644 --- a/unittest/test3.cc +++ b/unittest/test3.cc @@ -148,6 +148,13 @@ TEST_CASE("authorize user HOTP", "[pronew]") { } +TEST_CASE("check firmware version", "[pronew]") { + Stick10 stick; + connect_and_setup(stick); + + auto p = GetStatus::CommandTransaction::run(stick); + REQUIRE(p.data().firmware_version == 8); +} TEST_CASE("authorize user TOTP", "[pronew]") { Stick10 stick; -- cgit v1.2.1 From b94d61b2f3c446c46ac2f660d954841d740782f5 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Wed, 9 Nov 2016 19:20:51 +0100 Subject: Detect Pro 0.8 Signed-off-by: Szczepan Zalega --- unittest/misc.py | 4 ++++ unittest/test_pro.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/unittest/misc.py b/unittest/misc.py index b45436d..8296814 100644 --- a/unittest/misc.py +++ b/unittest/misc.py @@ -31,6 +31,10 @@ def is_pro_rtm_07(C): firmware = get_firmware_version_from_status(C) return '07 00' in firmware +def is_pro_rtm_08(C): + firmware = get_firmware_version_from_status(C) + return '08 00' in firmware + def is_storage(C): """ diff --git a/unittest/test_pro.py b/unittest/test_pro.py index 6ab2af9..c7772d6 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -2,7 +2,7 @@ import pytest from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET from misc import ffi, gs, wait, cast_pointer_to_tuple -from misc import is_pro_rtm_07, is_storage +from misc import is_pro_rtm_07, is_pro_rtm_08, is_storage def test_enable_password_safe(C): assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK @@ -61,7 +61,7 @@ def test_password_safe_slot_status(C): def test_issue_device_locks_on_second_key_generation_in_sequence(C): - if is_pro_rtm_07(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 -- cgit v1.2.1 From 2ba87abdb5c69e3d72b88164030ed5633986a63d Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Wed, 9 Nov 2016 19:22:46 +0100 Subject: Convinient function for checking if authorization command is supported Signed-off-by: Szczepan Zalega --- NitrokeyManager.cc | 16 +++++++++++++++- include/NitrokeyManager.h | 4 ++++ include/device.h | 9 +++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index 20f4f14..b254071 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -3,6 +3,7 @@ #include "include/NitrokeyManager.h" #include "include/LibraryException.h" #include +#include #include "include/misc.h" namespace nitrokey{ @@ -35,7 +36,11 @@ namespace nitrokey{ // package type to auth, auth type [Authorize,UserAuthorize] template - void authorize_packet(T &package, const char *admin_temporary_password, shared_ptr device){ + void NitrokeyManager::authorize_packet(T &package, const char *admin_temporary_password, shared_ptr device){ + if (!is_authorization_command_supported()){ + Log::instance()("Authorization command not supported, skipping", Loglevel::WARNING); + return; + } auto auth = get_payload(); strcpyT(auth.temporary_password, admin_temporary_password); auth.crc_to_authorize = S::CommandTransaction::getCRC(package); @@ -480,6 +485,15 @@ namespace nitrokey{ return v; } + bool NitrokeyManager::is_authorization_command_supported(){ + auto m = std::unordered_map({ + {DeviceModel::PRO, 7}, + {DeviceModel::STORAGE, 43}, + }); + auto status_p = GetStatus::CommandTransaction::run(*device); + return status_p.data().firmware_version <= m[device->get_device_model()]; + } + bool NitrokeyManager::is_AES_supported(const char *user_password) { auto a = get_payload(); strcpyT(a.user_password, user_password); diff --git a/include/NitrokeyManager.h b/include/NitrokeyManager.h index 60fa753..24a83ec 100644 --- a/include/NitrokeyManager.h +++ b/include/NitrokeyManager.h @@ -110,6 +110,10 @@ namespace nitrokey { int get_progress_bar_value(); ~NitrokeyManager(); + bool is_authorization_command_supported(); + + template + void authorize_packet(T &package, const char *admin_temporary_password, shared_ptr device); private: NitrokeyManager(); diff --git a/include/device.h b/include/device.h index 3f18921..62c4073 100644 --- a/include/device.h +++ b/include/device.h @@ -12,6 +12,15 @@ namespace nitrokey { namespace device { using namespace std::chrono_literals; + struct EnumClassHash + { + template + std::size_t operator()(T t) const + { + return static_cast(t); + } + }; + enum class DeviceModel{ PRO, STORAGE -- cgit v1.2.1 From acd426f8634678da15fc30f761f03e6520614fe0 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Thu, 10 Nov 2016 18:19:50 +0100 Subject: Add requirements for running pytest tests Signed-off-by: Szczepan Zalega --- unittest/requirements.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 unittest/requirements.txt diff --git a/unittest/requirements.txt b/unittest/requirements.txt new file mode 100644 index 0000000..7224741 --- /dev/null +++ b/unittest/requirements.txt @@ -0,0 +1,4 @@ +cffi +pytest-repeat +pytest-randomly +enum -- cgit v1.2.1 From c7e33af909a6e9883058685389aea7b5ad5b4dd1 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Thu, 10 Nov 2016 18:36:13 +0100 Subject: Mark unused packet variables in TOTP`s GET_CODE Signed-off-by: Szczepan Zalega --- include/stick10_commands_0.8.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/stick10_commands_0.8.h b/include/stick10_commands_0.8.h index 9099b3d..5e05405 100644 --- a/include/stick10_commands_0.8.h +++ b/include/stick10_commands_0.8.h @@ -252,9 +252,9 @@ namespace nitrokey { struct CommandPayload { uint8_t temporary_user_password[25]; uint8_t slot_number; - uint64_t challenge; - uint64_t last_totp_time; - uint8_t last_interval; + uint64_t challenge; //@unused + uint64_t last_totp_time; //@unused + uint8_t last_interval; //@unused bool isValid() const { return !(slot_number & 0xF0); } std::string dissect() const { -- cgit v1.2.1 From c51987f47307637beb6a1b75c351f273edda89cf Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Thu, 10 Nov 2016 18:40:32 +0100 Subject: Working support for new NK Pro 0.8 authorization-less format Signed-off-by: Szczepan Zalega --- NitrokeyManager.cc | 200 ++++++++++++++++++++++++++++++++++------------ include/NitrokeyManager.h | 16 ++++ 2 files changed, 164 insertions(+), 52 deletions(-) diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index b254071..e80f9b5 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -39,7 +39,6 @@ namespace nitrokey{ void NitrokeyManager::authorize_packet(T &package, const char *admin_temporary_password, shared_ptr device){ if (!is_authorization_command_supported()){ Log::instance()("Authorization command not supported, skipping", Loglevel::WARNING); - return; } auto auth = get_payload(); strcpyT(auth.temporary_password, admin_temporary_password); @@ -117,16 +116,25 @@ namespace nitrokey{ } uint32_t 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_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number); + + if (is_authorization_command_supported()){ auto gh = get_payload(); 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(gh, user_temporary_password, device); } - auto resp = GetHOTP::CommandTransaction::run(*device, gh); return resp.data().code; + } else { + auto gh = get_payload(); + 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 resp.data().code; + } } @@ -140,26 +148,41 @@ namespace nitrokey{ 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); - auto gt = get_payload(); - 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(gt, user_temporary_password, device); + if (is_authorization_command_supported()){ + auto gt = get_payload(); + 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(gt, user_temporary_password, device); + } + auto resp = GetTOTP::CommandTransaction::run(*device, gt); + return resp.data().code; + } else { + auto gt = get_payload(); + strcpyT(gt.temporary_user_password, user_temporary_password); + gt.slot_number = slot_number; + auto resp = stick10_08::GetTOTP::CommandTransaction::run(*device, gt); + return resp.data().code; } - auto resp = GetTOTP::CommandTransaction::run(*device, gt); - return resp.data().code; + } bool NitrokeyManager::erase_slot(uint8_t slot_number, const char *temporary_password) { + if (is_authorization_command_supported()){ auto p = get_payload(); p.slot_number = slot_number; - authorize_packet(p, temporary_password, device); - auto resp = EraseSlot::CommandTransaction::run(*device,p); + } else { + auto p = get_payload(); + p.slot_number = slot_number; + strcpyT(p.temporary_admin_password, temporary_password); + auto resp = stick10_08::EraseSlot::CommandTransaction::run(*device,p); + } return true; } @@ -191,59 +214,130 @@ namespace nitrokey{ if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number); slot_number = get_internal_slot_number_for_hotp(slot_number); - auto payload = get_payload(); - 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); + if (is_authorization_command_supported()){ + write_HOTP_slot_authorize(slot_number, slot_name, secret, hotp_counter, use_8_digits, use_enter, use_tokenID, + token_ID, temporary_password); + } else { + write_HOTP_slot_no_authorize(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_no_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) const { + auto payload = get_payload(); + strcpyT(payload.temporary_admin_password, temporary_password); + auto secret_bin = misc::hex_string_to_byte(secret); + vector_copy(payload.slot_secret, secret_bin); + strcpyT(payload.slot_token_id, token_ID); + payload.use_8_digits = use_8_digits; + payload.use_enter = use_enter; + payload.use_tokenID = use_tokenID; + + auto payload2 = get_payload(); + strcpyT(payload2.temporary_admin_password, temporary_password); + payload2.slot_number = slot_number; + strcpyT(payload2.slot_name, slot_name); + payload2.slot_counter = hotp_counter; + + stick10_08::WriteToHOTPSlot::CommandTransaction::run(*device, payload); + stick10_08::WriteToHOTPSlot_2::CommandTransaction::run(*device, payload2); + } + + 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(); + 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: { - std::string counter = std::to_string(hotp_counter); + string counter = to_string(hotp_counter); strcpyT(payload.slot_counter_s, counter.c_str()); break; } default: - nitrokey::log::Log::instance()( std::string(__FILE__) + std::to_string(__LINE__) + - std::string(__FUNCTION__) + std::string(" Unhandled device model for HOTP") - , nitrokey::log::Loglevel::DEBUG); + Log::instance()(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; + payload.use_8_digits = use_8_digits; + payload.use_enter = use_enter; + payload.use_tokenID = use_tokenID; - authorize_packet(payload, temporary_password, device); + authorize_packet(payload, temporary_password, device); - auto resp = WriteToHOTPSlot::CommandTransaction::run(*device, payload); - return true; + 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) { - auto payload = get_payload(); if (!is_valid_totp_slot_number(slot_number)) throw InvalidSlotException(slot_number); - slot_number = get_internal_slot_number_for_totp(slot_number); - 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(payload, temporary_password, device); - - auto resp = WriteToTOTPSlot::CommandTransaction::run(*device, payload); - return true; + + if (is_authorization_command_supported()){ + write_TOTP_slot_authorize(slot_number, slot_name, secret, time_window, use_8_digits, use_enter, use_tokenID, + token_ID, temporary_password); + } else { + write_TOTP_slot_no_authorize(slot_number, slot_name, secret, time_window, use_8_digits, use_enter, use_tokenID, + token_ID, temporary_password); + } + + return true; + } + + void NitrokeyManager::write_TOTP_slot_no_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) const { + auto payload = get_payload(); + strcpyT(payload.temporary_admin_password, temporary_password); + auto secret_bin = misc::hex_string_to_byte(secret); + vector_copy(payload.slot_secret, secret_bin); + strcpyT(payload.slot_token_id, token_ID); + payload.use_8_digits = use_8_digits; + payload.use_enter = use_enter; + payload.use_tokenID = use_tokenID; + + auto payload2 = get_payload(); + strcpyT(payload2.temporary_admin_password, temporary_password); + payload2.slot_number = slot_number; + strcpyT(payload2.slot_name, slot_name); + payload2.slot_interval= time_window; + + stick10_08::WriteToTOTPSlot::CommandTransaction::run(*device, payload); + stick10_08::WriteToTOTPSlot_2::CommandTransaction::run(*device, payload2); + } + + 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(); + 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(payload, temporary_password, device); + + auto resp = WriteToTOTPSlot::CommandTransaction::run(*device, payload); } const char * NitrokeyManager::get_totp_slot_name(uint8_t slot_number) { @@ -466,16 +560,18 @@ namespace nitrokey{ 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(); + auto p = get_payload(); 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; - - authorize_packet(p, admin_temporary_password, device); - - WriteGeneralConfig::CommandTransaction::run(*device, p); + if (is_authorization_command_supported()){ + authorize_packet(p, admin_temporary_password, device); + } else { + strcpyT(p.temporary_admin_password, admin_temporary_password); + } + stick10_08::WriteGeneralConfig::CommandTransaction::run(*device, p); } vector NitrokeyManager::read_config() { diff --git a/include/NitrokeyManager.h b/include/NitrokeyManager.h index 24a83ec..c0064f7 100644 --- a/include/NitrokeyManager.h +++ b/include/NitrokeyManager.h @@ -5,6 +5,7 @@ #include "log.h" #include "device_proto.h" #include "stick10_commands.h" +#include "stick10_commands_0.8.h" #include "stick20_commands.h" #include #include @@ -132,6 +133,21 @@ namespace nitrokey { template void change_PIN_general(char *current_PIN, 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_HOTP_slot_no_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) const; + + 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_TOTP_slot_no_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) const; }; } -- cgit v1.2.1 From 3ab15750995624222fa32927fee7f9b1598ba3bf Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Sat, 12 Nov 2016 11:06:11 +0100 Subject: Move temporary_password to packet end To allow read-only backward compatibility for GET_CODE Signed-off-by: Szczepan Zalega --- include/stick10_commands_0.8.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/include/stick10_commands_0.8.h b/include/stick10_commands_0.8.h index 5e05405..3644c4d 100644 --- a/include/stick10_commands_0.8.h +++ b/include/stick10_commands_0.8.h @@ -201,8 +201,13 @@ namespace nitrokey { class GetHOTP : Command { public: struct CommandPayload { - uint8_t temporary_user_password[25]; 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 { @@ -250,11 +255,11 @@ namespace nitrokey { //user auth public: struct CommandPayload { - uint8_t temporary_user_password[25]; 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 { -- cgit v1.2.1 From d28794ef7d6d6fa7e8d6478fb912302691fe75e7 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Sat, 12 Nov 2016 18:34:23 +0100 Subject: Add test for STATUS_AES_DEC_FAILED error Signed-off-by: Szczepan Zalega --- unittest/test_pro.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/unittest/test_pro.py b/unittest/test_pro.py index c7772d6..5c8ecb4 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -74,6 +74,14 @@ def test_regenerate_aes_key(C): assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK +def test_enable_password_safe_after_factory_reset(C): + assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK + assert C.NK_factory_reset(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK + wait(10) + assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_AES_DEC_FAILED + assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK + assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK + @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): @@ -96,6 +104,7 @@ def test_destroy_password_safe(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_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)) != 'slotname1' -- cgit v1.2.1 From 411921354998d01229ba1cd10424df05441825be Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Tue, 15 Nov 2016 20:17:02 +0100 Subject: Tests: secret started with null byte Signed-off-by: Szczepan Zalega --- unittest/test_pro.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/unittest/test_pro.py b/unittest/test_pro.py index 5c8ecb4..8c99f81 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -74,6 +74,7 @@ def test_regenerate_aes_key(C): assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK + def test_enable_password_safe_after_factory_reset(C): assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK assert C.NK_factory_reset(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK @@ -294,6 +295,7 @@ def test_HOTP_64bit_counter(C): assert C.NK_write_hotp_slot(slot_number, 'python_test', RFC_SECRET, t, use_8_digits, False, False, "", DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK code_device = str(C.NK_get_hotp_code(slot_number)) + code_device = '0'+code_device if len(code_device) < 6 else code_device dev_res += (t, code_device) lib_res += (t, lib_at(t)) assert dev_res == lib_res @@ -319,6 +321,7 @@ def test_TOTP_64bit_time(C): 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 = str((C.NK_get_totp_code(slot_number, T, 0, 30))) + code_device = '0'+code_device if len(code_device) < 6 else code_device dev_res += (t, code_device) lib_res += (t, lib_at(t)) assert dev_res == lib_res @@ -495,3 +498,25 @@ def test_get_serial_number(C): sn = gs(sn) assert len(sn) > 0 print(('Serial number of the device: ', sn)) + +@pytest.mark.parametrize("secret", ['000001', '00'*10+'ff', '00'*19+'ff', '000102', '002EF43F51AFA97BA2B46418768123C9E1809A5B' ]) +def test_OTP_secret_started_from_null(C, secret): + oath = pytest.importorskip("oath") + lib_at = lambda t: 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, 'null_secret', secret, t, use_8_digits, False, False, "", + DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + code_device = str(C.NK_get_hotp_code(slot_number)) + code_device = '0'+code_device if len(code_device) < 6 else code_device + dev_res += (t, code_device) + lib_res += (t, lib_at(t)) + assert dev_res == lib_res -- cgit v1.2.1 From 54d3b649b4c6d51120c16c64b7f824c81123a807 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Wed, 16 Nov 2016 11:25:17 +0100 Subject: Tests: more OTP, test all slots for read/write Signed-off-by: Szczepan Zalega --- unittest/test_pro.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/unittest/test_pro.py b/unittest/test_pro.py index 8c99f81..a9e9fa4 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -520,3 +520,57 @@ def test_OTP_secret_started_from_null(C, secret): dev_res += (t, code_device) lib_res += (t, lib_at(t)) assert dev_res == lib_res + + +@pytest.mark.parametrize("counter", [0, 3, 7, 0xffff] ) +def test_HOTP_slots_read_write_counter(C, counter): + secret = RFC_SECRET + oath = pytest.importorskip("oath") + lib_at = lambda t: 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, 'null_secret', secret, counter, use_8_digits, False, False, "", + DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + code_device = str(C.NK_get_hotp_code(slot_number)) + code_device = '0'+code_device if len(code_device) < 6 else code_device + dev_res += (counter, code_device) + lib_res += (counter, lib_at(counter)) + assert dev_res == lib_res + + +@pytest.mark.parametrize("period", [30,60] ) +@pytest.mark.parametrize("time", range(20,70,20) ) +def test_TOTP_slots_read_write_at_time_period(C, time, period): + secret = RFC_SECRET + oath = pytest.importorskip("oath") + lib_at = lambda t: 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, 'null_secret', secret, period, use_8_digits, False, False, "", + 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 = str(C.NK_get_totp_code(slot_number, T, 0, period)) + code_device = '0'+code_device if len(code_device) < 6 else code_device + dev_res += (time, code_device) + lib_res += (time, lib_at(time)) + assert dev_res == lib_res + + + + -- cgit v1.2.1 From cbccc871329c5522449010ae5007278123508820 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Wed, 16 Nov 2016 18:32:38 +0100 Subject: Use another OTP writing protocol and test it Signed-off-by: Szczepan Zalega --- NitrokeyManager.cc | 67 +++++++++++++++---------- command_id.cc | 3 ++ include/command_id.h | 1 + include/stick10_commands_0.8.h | 111 +++++++++-------------------------------- unittest/constants.py | 2 +- unittest/test3.cc | 111 ++++++++++++++++++++++++++--------------- unittest/test_pro.py | 33 ++++++++++-- 7 files changed, 169 insertions(+), 159 deletions(-) diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index e80f9b5..46c09df 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -228,23 +228,30 @@ namespace nitrokey{ uint64_t hotp_counter, bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, const char *temporary_password) const { - auto payload = get_payload(); - strcpyT(payload.temporary_admin_password, temporary_password); + auto payload2 = get_payload(); + strcpyT(payload2.temporary_admin_password, temporary_password); + strcpyT(payload2.data, slot_name); + payload2.length = strlen((const char *) payload2.data); + payload2.setTypeName(); + stick10_08::SendOTPData::CommandTransaction::run(*device, payload2); + + payload2 = get_payload(); + strcpyT(payload2.temporary_admin_password, temporary_password); auto secret_bin = misc::hex_string_to_byte(secret); - vector_copy(payload.slot_secret, secret_bin); + vector_copy(payload2.data, secret_bin); + payload2.length = strlen((const char *) payload2.data); + payload2.setTypeSecret(); + stick10_08::SendOTPData::CommandTransaction::run(*device, payload2); + + auto payload = get_payload(); + 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; - - auto payload2 = get_payload(); - strcpyT(payload2.temporary_admin_password, temporary_password); - payload2.slot_number = slot_number; - strcpyT(payload2.slot_name, slot_name); - payload2.slot_counter = hotp_counter; - - stick10_08::WriteToHOTPSlot::CommandTransaction::run(*device, payload); - stick10_08::WriteToHOTPSlot_2::CommandTransaction::run(*device, payload2); + payload.slot_counter_or_interval = hotp_counter; + payload.slot_number = slot_number; + stick10_08::WriteToOTPSlot::CommandTransaction::run(*device, payload); } void NitrokeyManager::write_HOTP_slot_authorize(uint8_t slot_number, const char *slot_name, const char *secret, @@ -302,23 +309,31 @@ namespace nitrokey{ uint16_t time_window, bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, const char *temporary_password) const { - auto payload = get_payload(); - strcpyT(payload.temporary_admin_password, temporary_password); + + auto payload2 = get_payload(); + strcpyT(payload2.temporary_admin_password, temporary_password); + strcpyT(payload2.data, slot_name); + payload2.length = strlen((const char *) payload2.data); + payload2.setTypeName(); + stick10_08::SendOTPData::CommandTransaction::run(*device, payload2); + + payload2 = get_payload(); + strcpyT(payload2.temporary_admin_password, temporary_password); auto secret_bin = misc::hex_string_to_byte(secret); - vector_copy(payload.slot_secret, secret_bin); + vector_copy(payload2.data, secret_bin); + payload2.length = strlen((const char *) payload2.data); + payload2.setTypeSecret(); + stick10_08::SendOTPData::CommandTransaction::run(*device, payload2); + + auto payload = get_payload(); + 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; - - auto payload2 = get_payload(); - strcpyT(payload2.temporary_admin_password, temporary_password); - payload2.slot_number = slot_number; - strcpyT(payload2.slot_name, slot_name); - payload2.slot_interval= time_window; - - stick10_08::WriteToTOTPSlot::CommandTransaction::run(*device, payload); - stick10_08::WriteToTOTPSlot_2::CommandTransaction::run(*device, payload2); + payload.slot_counter_or_interval = time_window; + payload.slot_number = 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, @@ -583,8 +598,8 @@ namespace nitrokey{ bool NitrokeyManager::is_authorization_command_supported(){ auto m = std::unordered_map({ - {DeviceModel::PRO, 7}, - {DeviceModel::STORAGE, 43}, + {DeviceModel::PRO, 7}, + {DeviceModel::STORAGE, 43}, }); auto status_p = GetStatus::CommandTransaction::run(*device); return status_p.data().firmware_version <= m[device->get_device_model()]; diff --git a/command_id.cc b/command_id.cc index a93d05c..f76a358 100644 --- a/command_id.cc +++ b/command_id.cc @@ -142,6 +142,9 @@ const char *commandid_to_string(CommandID id) { 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/include/command_id.h b/include/command_id.h index 1f7affc..346b750 100644 --- a/include/command_id.h +++ b/include/command_id.h @@ -66,6 +66,7 @@ enum class CommandID : uint8_t { 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, //@unused diff --git a/include/stick10_commands_0.8.h b/include/stick10_commands_0.8.h index 3644c4d..e880c0a 100644 --- a/include/stick10_commands_0.8.h +++ b/include/stick10_commands_0.8.h @@ -44,47 +44,33 @@ namespace nitrokey { CommandTransaction; }; - class WriteToHOTPSlot : Command { + class SendOTPData : Command { //admin auth public: struct CommandPayload { uint8_t temporary_admin_password[25]; - 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; - }; + uint8_t type; //0-secret, 1-name + uint8_t id; //multiple reports for values longer than 30 bytes + uint8_t length; //data length + 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; ss << "temporary_admin_password:\t" << temporary_admin_password << std::endl; - ss << "slot_secret:" << std::endl - << ::nitrokey::misc::hexdump((const char *) (&slot_secret), sizeof 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 << "type:\t" << type << std::endl; + ss << "id:\t" << (int)id << std::endl; + ss << "length:\t" << (int)length << std::endl; + ss << "data:" << std::endl + << ::nitrokey::misc::hexdump((const char *) (&data), sizeof data); return ss.str(); } } __packed; @@ -93,42 +79,16 @@ namespace nitrokey { CommandTransaction; }; - class WriteToHOTPSlot_2 : Command { + class WriteToOTPSlot : Command { + //admin auth public: struct CommandPayload { uint8_t temporary_admin_password[25]; uint8_t slot_number; - uint8_t slot_name[15]; union { - uint64_t slot_counter; + uint64_t slot_counter_or_interval; uint8_t slot_counter_s[8]; } __packed; - - bool isValid() const { return !(slot_number & 0xF0); } - - std::string dissect() const { - std::stringstream ss; - ss << "temporary_admin_password:\t" << temporary_admin_password << std::endl; - ss << "slot_number:\t" << (int) (slot_number) << std::endl; - ss << "slot_name:\t" << slot_name << std::endl; - ss << "slot_counter:\t[" << (int) slot_counter << "]\t" - << ::nitrokey::misc::hexdump((const char *) (&slot_counter), sizeof slot_counter, false); - - return ss.str(); - } - } __packed; - - typedef Transaction - CommandTransaction; - }; - - - class WriteToTOTPSlot : Command { - //admin auth - public: - struct CommandPayload { - uint8_t temporary_admin_password[25]; - uint8_t slot_secret[20]; union { uint8_t _slot_config; struct { @@ -152,12 +112,13 @@ namespace nitrokey { std::string dissect() const { std::stringstream ss; ss << "temporary_admin_password:\t" << temporary_admin_password << std::endl; - ss << "slot_secret:" << std::endl - << ::nitrokey::misc::hexdump((const char *) (&slot_secret), sizeof 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_number:\t" << (int) (slot_number) << std::endl; + ss << "slot_counter_or_interval:\t[" << (int) slot_counter_or_interval << "]\t" + << ::nitrokey::misc::hexdump((const char *) (&slot_counter_or_interval), sizeof slot_counter_or_interval, false); ss << "slot_token_id:\t"; for (auto i : slot_token_id) @@ -172,32 +133,6 @@ namespace nitrokey { CommandTransaction; }; - class WriteToTOTPSlot_2 : Command { - public: - struct CommandPayload { - uint8_t temporary_admin_password[25]; - uint8_t slot_number; - uint8_t slot_name[15]; - uint16_t slot_interval; - - bool isValid() const { return !(slot_number & 0xF0); } - - std::string dissect() const { - std::stringstream ss; - ss << "temporary_admin_password:\t" << temporary_admin_password << std::endl; - ss << "slot_number:\t" << (int) (slot_number) << std::endl; - ss << "slot_name:\t" << slot_name << std::endl; - ss << "slot_interval:\t" << (int)slot_interval << std::endl; - - return ss.str(); - } - } __packed; - - typedef Transaction - CommandTransaction; - }; - - class GetHOTP : Command { public: struct CommandPayload { diff --git a/unittest/constants.py b/unittest/constants.py index 78a219b..3c19a9b 100644 --- a/unittest/constants.py +++ b/unittest/constants.py @@ -2,7 +2,7 @@ from enum import Enum from misc import to_hex RFC_SECRET_HR = '12345678901234567890' -RFC_SECRET = to_hex(RFC_SECRET_HR) # '12345678901234567890' +RFC_SECRET = to_hex(RFC_SECRET_HR) # '3031323334353637383930...' # print( repr((RFC_SECRET, RFC_SECRET_, len(RFC_SECRET))) ) diff --git a/unittest/test3.cc b/unittest/test3.cc index 7f779a5..7b37a60 100644 --- a/unittest/test3.cc +++ b/unittest/test3.cc @@ -49,18 +49,26 @@ TEST_CASE("write slot", "[pronew]"){ connect_and_setup(stick); authorize(stick); - auto p = get_payload(); - strcpyT(p.slot_secret, RFC_SECRET); - strcpyT(p.temporary_admin_password, temporary_password); - p.use_8_digits = true; - stick10_08::WriteToHOTPSlot::CommandTransaction::run(stick, p); + auto p2 = get_payload(); + strcpyT(p2.temporary_admin_password, temporary_password); + p2.setTypeName(); + strcpyT(p2.data, "test name aaa"); + p2.length = strlen((const char *) p2.data); + stick10_08::SendOTPData::CommandTransaction::run(stick, p2); - auto p2 = get_payload(); + p2 = get_payload(); strcpyT(p2.temporary_admin_password, temporary_password); - p2.slot_number = 0 + 0x10; - p2.slot_counter = 0; - strcpyT(p2.slot_name, "test name aaa"); - stick10_08::WriteToHOTPSlot_2::CommandTransaction::run(stick, p2); + strcpyT(p2.data, RFC_SECRET); + p2.length = strlen(RFC_SECRET); + p2.setTypeSecret(); + stick10_08::SendOTPData::CommandTransaction::run(stick, p2); + + auto p = get_payload(); + 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(); pc.enable_user_password = 0; @@ -119,23 +127,34 @@ TEST_CASE("authorize user HOTP", "[pronew]") { connect_and_setup(stick); authorize(stick); - auto p = get_payload(); - p.enable_user_password = 1; - strcpyT(p.temporary_admin_password, temporary_password); - WriteGeneralConfig::CommandTransaction::run(stick, p); + { + auto p = get_payload(); + p.enable_user_password = 1; + strcpyT(p.temporary_admin_password, temporary_password); + WriteGeneralConfig::CommandTransaction::run(stick, p); + } + + auto p2 = get_payload(); + strcpyT(p2.temporary_admin_password, temporary_password); + p2.setTypeName(); + strcpyT(p2.data, "test name aaa"); + p2.length = strlen((const char *) p2.data); + stick10_08::SendOTPData::CommandTransaction::run(stick, p2); + + p2 = get_payload(); + strcpyT(p2.temporary_admin_password, temporary_password); + strcpyT(p2.data, RFC_SECRET); + p2.length = strlen(RFC_SECRET); + p2.setTypeSecret(); + stick10_08::SendOTPData::CommandTransaction::run(stick, p2); - auto pw = get_payload(); - strcpyT(pw.slot_secret, RFC_SECRET); - strcpyT(pw.temporary_admin_password, temporary_password); - pw.use_8_digits = true; - WriteToHOTPSlot::CommandTransaction::run(stick, pw); + auto p = get_payload(); + 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 pw2 = get_payload(); - strcpyT(pw2.temporary_admin_password, temporary_password); - pw2.slot_number = 0 + 0x10; - pw2.slot_counter = 0; - strcpyT(pw2.slot_name, "test name aaa"); - WriteToHOTPSlot_2::CommandTransaction::run(stick, pw2); auto p3 = get_payload(); p3.slot_number = 0 + 0x10; @@ -161,23 +180,33 @@ TEST_CASE("authorize user TOTP", "[pronew]") { connect_and_setup(stick); authorize(stick); - auto p = get_payload(); - p.enable_user_password = 1; - strcpyT(p.temporary_admin_password, temporary_password); - WriteGeneralConfig::CommandTransaction::run(stick, p); + { + auto p = get_payload(); + p.enable_user_password = 1; + strcpyT(p.temporary_admin_password, temporary_password); + WriteGeneralConfig::CommandTransaction::run(stick, p); + } - auto pw = get_payload(); - strcpyT(pw.slot_secret, RFC_SECRET); - strcpyT(pw.temporary_admin_password, temporary_password); - pw.use_8_digits = true; - WriteToTOTPSlot::CommandTransaction::run(stick, pw); - - auto pw2 = get_payload(); - strcpyT(pw2.temporary_admin_password, temporary_password); - pw2.slot_number = 0 + 0x20; - pw2.slot_interval= 30; - strcpyT(pw2.slot_name, "test name TOTP"); - WriteToTOTPSlot_2::CommandTransaction::run(stick, pw2); + auto p2 = get_payload(); + strcpyT(p2.temporary_admin_password, temporary_password); + p2.setTypeName(); + strcpyT(p2.data, "test name TOTP"); + p2.length = strlen((const char *) p2.data); + stick10_08::SendOTPData::CommandTransaction::run(stick, p2); + + p2 = get_payload(); + strcpyT(p2.temporary_admin_password, temporary_password); + strcpyT(p2.data, RFC_SECRET); + p2.length = strlen(RFC_SECRET); + p2.setTypeSecret(); + stick10_08::SendOTPData::CommandTransaction::run(stick, p2); + + auto p = get_payload(); + 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(); p_get_totp.slot_number = 0 + 0x20; diff --git a/unittest/test_pro.py b/unittest/test_pro.py index a9e9fa4..3632ecd 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -536,7 +536,7 @@ def test_HOTP_slots_read_write_counter(C, counter): 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, 'null_secret', secret, counter, use_8_digits, False, False, "", + assert C.NK_write_hotp_slot(slot_number, 'HOTP rw' + str(slot_number), secret, counter, use_8_digits, False, False, "", DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK code_device = str(C.NK_get_hotp_code(slot_number)) code_device = '0'+code_device if len(code_device) < 6 else code_device @@ -546,7 +546,7 @@ def test_HOTP_slots_read_write_counter(C, counter): @pytest.mark.parametrize("period", [30,60] ) -@pytest.mark.parametrize("time", range(20,70,20) ) +@pytest.mark.parametrize("time", range(21,70,20) ) def test_TOTP_slots_read_write_at_time_period(C, time, period): secret = RFC_SECRET oath = pytest.importorskip("oath") @@ -561,7 +561,7 @@ def test_TOTP_slots_read_write_at_time_period(C, time, period): 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, 'null_secret', secret, period, use_8_digits, False, False, "", + assert C.NK_write_totp_slot(slot_number, 'TOTP rw' + str(slot_number), secret, period, use_8_digits, False, False, "", 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 @@ -572,5 +572,32 @@ def test_TOTP_slots_read_write_at_time_period(C, time, period): assert dev_res == lib_res +@pytest.mark.parametrize("secret", [RFC_SECRET, 2*RFC_SECRET] ) +def test_TOTP_secrets(C, secret): + slot_number = 0 + time = 0 + period = 30 + oath = pytest.importorskip("oath") + lib_at = lambda t: 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_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert C.NK_write_totp_slot(slot_number, 'TOTP secret' + str(slot_number), secret, period, use_8_digits, False, False, "", + 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 = str(C.NK_get_totp_code(slot_number, T, 0, period)) + code_device = '0'+code_device if len(code_device) < 6 else code_device + dev_res += (time, code_device) + lib_res += (time, lib_at(time)) + assert dev_res == lib_res + + -- cgit v1.2.1 From 9c2feef240e396648dfb2378f7d2428b0593c9f2 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 18 Nov 2016 12:52:50 +0100 Subject: Support longer secrets (40 bytes) for NK Pro 0.8 Signed-off-by: Szczepan Zalega --- NitrokeyManager.cc | 29 +++++++++++++++++++++------ include/stick10_commands_0.8.h | 18 ++++++++++++++++- misc.cc | 5 +++-- unittest/test_pro.py | 45 ++++++++++++++++++++++++++++++++++-------- 4 files changed, 80 insertions(+), 17 deletions(-) diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index 46c09df..7cc88eb 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -198,6 +198,16 @@ namespace nitrokey{ return erase_slot(slot_number, temporary_password); } + template + void vector_copy_ranged(T& dest, std::vector &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 void vector_copy(T& dest, std::vector &vec){ const size_t d_size = sizeof(dest); @@ -317,13 +327,20 @@ namespace nitrokey{ payload2.setTypeName(); stick10_08::SendOTPData::CommandTransaction::run(*device, payload2); - payload2 = get_payload(); - strcpyT(payload2.temporary_admin_password, temporary_password); - auto secret_bin = misc::hex_string_to_byte(secret); - vector_copy(payload2.data, secret_bin); - payload2.length = strlen((const char *) payload2.data); payload2.setTypeSecret(); - stick10_08::SendOTPData::CommandTransaction::run(*device, payload2); + payload2.id = 0; + auto secret_bin = misc::hex_string_to_byte(secret); + auto remaining_secret_length = secret_bin.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(); strcpyT(payload.temporary_admin_password, temporary_password); diff --git a/include/stick10_commands_0.8.h b/include/stick10_commands_0.8.h index e880c0a..f0e28a6 100644 --- a/include/stick10_commands_0.8.h +++ b/include/stick10_commands_0.8.h @@ -75,7 +75,23 @@ namespace nitrokey { } } __packed; - typedef Transaction + + struct ResponsePayload { + union { + uint8_t data[40]; + } __packed; + + bool isValid() const { return true; } + std::string dissect() const { + std::stringstream ss; + ss << "data:" << std::endl + << ::nitrokey::misc::hexdump((const char *) (&data), sizeof data); + return ss.str(); + } + } __packed; + + + typedef Transaction CommandTransaction; }; diff --git a/misc.cc b/misc.cc index c9d38cb..7a3c199 100644 --- a/misc.cc +++ b/misc.cc @@ -16,7 +16,8 @@ std::vector hex_string_to_byte(const char* hexString){ if (s_size%2!=0 || s_size==0 || s_size>big_string_size){ throw InvalidHexString(0); } - auto data = std::vector(d_size, 0); + auto data = std::vector(); + data.reserve(d_size); char buf[2]; for(int i=0; i hex_string_to_byte(const char* hexString){ } buf[i%2] = c; if (i%2==1){ - data[i/2] = strtoul(buf, NULL, 16) & 0xFF; + data.push_back( strtoul(buf, NULL, 16) & 0xFF ); } } return data; diff --git a/unittest/test_pro.py b/unittest/test_pro.py index 3632ecd..71abfba 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -501,6 +501,9 @@ def test_get_serial_number(C): @pytest.mark.parametrize("secret", ['000001', '00'*10+'ff', '00'*19+'ff', '000102', '002EF43F51AFA97BA2B46418768123C9E1809A5B' ]) def test_OTP_secret_started_from_null(C, secret): + ''' + NK Pro 0.8+, NK Storage 0.43+ + ''' oath = pytest.importorskip("oath") lib_at = lambda t: oath.hotp(secret, t, format='dec6') PIN_protection = False @@ -522,7 +525,7 @@ def test_OTP_secret_started_from_null(C, secret): assert dev_res == lib_res -@pytest.mark.parametrize("counter", [0, 3, 7, 0xffff] ) +@pytest.mark.parametrize("counter", [0, 3, 7, 0xffff, 0xffffffff, 0xffffffffffffffff] ) def test_HOTP_slots_read_write_counter(C, counter): secret = RFC_SECRET oath = pytest.importorskip("oath") @@ -571,9 +574,13 @@ def test_TOTP_slots_read_write_at_time_period(C, time, period): lib_res += (time, lib_at(time)) assert dev_res == lib_res - -@pytest.mark.parametrize("secret", [RFC_SECRET, 2*RFC_SECRET] ) +@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+ + ''' + if is_pro_rtm_07(C) and 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 ") slot_number = 0 time = 0 period = 30 @@ -587,10 +594,8 @@ def test_TOTP_secrets(C, secret): DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK dev_res = [] lib_res = [] - assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK - assert C.NK_write_totp_slot(slot_number, 'TOTP secret' + str(slot_number), secret, period, use_8_digits, False, False, "", + assert C.NK_write_totp_slot(slot_number, 'secret' + str(len(secret)), secret, period, use_8_digits, False, False, "", 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 = str(C.NK_get_totp_code(slot_number, T, 0, period)) code_device = '0'+code_device if len(code_device) < 6 else code_device @@ -598,6 +603,30 @@ def test_TOTP_secrets(C, secret): lib_res += (time, lib_at(time)) assert dev_res == lib_res - - +@pytest.mark.parametrize("secret", [RFC_SECRET, 2*RFC_SECRET, '12'*10, '12'*30] ) +def test_HOTP_secrets(C, secret): + ''' + NK Pro 0.8+, NK Storage 0.44+ + ''' + if is_pro_rtm_07(C) and 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 ") + slot_number = 0 + counter = 0 + oath = pytest.importorskip("oath") + lib_at = lambda t: 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 = [] + assert C.NK_write_hotp_slot(slot_number, 'secret' + str(len(secret)), secret, counter, use_8_digits, False, False, "", + DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + code_device = str(C.NK_get_hotp_code(slot_number)) + code_device = '0'+code_device if len(code_device) < 6 else code_device + dev_res += (counter, code_device) + lib_res += (counter, lib_at(counter)) + assert dev_res == lib_res -- cgit v1.2.1 From f615000166177dad7128247d5c99679d9560c510 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Sat, 19 Nov 2016 14:09:40 +0100 Subject: Remove length field from send_otp_data packet Signed-off-by: Szczepan Zalega --- NitrokeyManager.cc | 1 - include/stick10_commands_0.8.h | 2 -- unittest/test3.cc | 6 ------ 3 files changed, 9 deletions(-) diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index 7cc88eb..b991af1 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -323,7 +323,6 @@ namespace nitrokey{ auto payload2 = get_payload(); strcpyT(payload2.temporary_admin_password, temporary_password); strcpyT(payload2.data, slot_name); - payload2.length = strlen((const char *) payload2.data); payload2.setTypeName(); stick10_08::SendOTPData::CommandTransaction::run(*device, payload2); diff --git a/include/stick10_commands_0.8.h b/include/stick10_commands_0.8.h index f0e28a6..f905794 100644 --- a/include/stick10_commands_0.8.h +++ b/include/stick10_commands_0.8.h @@ -51,7 +51,6 @@ namespace nitrokey { uint8_t temporary_admin_password[25]; uint8_t type; //0-secret, 1-name uint8_t id; //multiple reports for values longer than 30 bytes - uint8_t length; //data length uint8_t data[30]; //data, does not need null termination bool isValid() const { return true; } @@ -68,7 +67,6 @@ namespace nitrokey { ss << "temporary_admin_password:\t" << temporary_admin_password << std::endl; ss << "type:\t" << type << std::endl; ss << "id:\t" << (int)id << std::endl; - ss << "length:\t" << (int)length << std::endl; ss << "data:" << std::endl << ::nitrokey::misc::hexdump((const char *) (&data), sizeof data); return ss.str(); diff --git a/unittest/test3.cc b/unittest/test3.cc index 7b37a60..9049365 100644 --- a/unittest/test3.cc +++ b/unittest/test3.cc @@ -53,13 +53,11 @@ TEST_CASE("write slot", "[pronew]"){ strcpyT(p2.temporary_admin_password, temporary_password); p2.setTypeName(); strcpyT(p2.data, "test name aaa"); - p2.length = strlen((const char *) p2.data); stick10_08::SendOTPData::CommandTransaction::run(stick, p2); p2 = get_payload(); strcpyT(p2.temporary_admin_password, temporary_password); strcpyT(p2.data, RFC_SECRET); - p2.length = strlen(RFC_SECRET); p2.setTypeSecret(); stick10_08::SendOTPData::CommandTransaction::run(stick, p2); @@ -138,13 +136,11 @@ TEST_CASE("authorize user HOTP", "[pronew]") { strcpyT(p2.temporary_admin_password, temporary_password); p2.setTypeName(); strcpyT(p2.data, "test name aaa"); - p2.length = strlen((const char *) p2.data); stick10_08::SendOTPData::CommandTransaction::run(stick, p2); p2 = get_payload(); strcpyT(p2.temporary_admin_password, temporary_password); strcpyT(p2.data, RFC_SECRET); - p2.length = strlen(RFC_SECRET); p2.setTypeSecret(); stick10_08::SendOTPData::CommandTransaction::run(stick, p2); @@ -191,13 +187,11 @@ TEST_CASE("authorize user TOTP", "[pronew]") { strcpyT(p2.temporary_admin_password, temporary_password); p2.setTypeName(); strcpyT(p2.data, "test name TOTP"); - p2.length = strlen((const char *) p2.data); stick10_08::SendOTPData::CommandTransaction::run(stick, p2); p2 = get_payload(); strcpyT(p2.temporary_admin_password, temporary_password); strcpyT(p2.data, RFC_SECRET); - p2.length = strlen(RFC_SECRET); p2.setTypeSecret(); stick10_08::SendOTPData::CommandTransaction::run(stick, p2); -- cgit v1.2.1 From b33681083348588caa3db3885c811a3c42d5b094 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Sat, 19 Nov 2016 14:14:43 +0100 Subject: Support sending empty OTP secrets for slot edit (+test) Signed-off-by: Szczepan Zalega --- misc.cc | 2 +- unittest/test_pro.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/misc.cc b/misc.cc index 7a3c199..3f15520 100644 --- a/misc.cc +++ b/misc.cc @@ -13,7 +13,7 @@ std::vector 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==0 || s_size>big_string_size){ + if (s_size%2!=0 || s_size>big_string_size){ throw InvalidHexString(0); } auto data = std::vector(); diff --git a/unittest/test_pro.py b/unittest/test_pro.py index 71abfba..b8109f2 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -630,3 +630,34 @@ def test_HOTP_secrets(C, secret): lib_res += (counter, lib_at(counter)) assert dev_res == lib_res + +def test_edit_OTP_slot(C): + """ + should change slots counter and name without changing its secret (using null secret for second update) + """ + 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 = 'edit slot' + assert C.NK_write_hotp_slot(slot_number, first_name, secret, counter, use_8_digits, False, False, "", + DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert gs(C.NK_get_hotp_slot_name(slot_number)) == first_name + + + first_code = C.NK_get_hotp_code(slot_number) + changed_name = 'changedname' + empty_secret = '' + 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, "", + DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + second_code = C.NK_get_hotp_code(slot_number) + assert first_code == second_code + assert gs(C.NK_get_hotp_slot_name(slot_number)) == changed_name + + -- cgit v1.2.1 From fbe8f668eb3ceb02a23f943d5db5070b0cafc401 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Sat, 19 Nov 2016 14:16:44 +0100 Subject: Test to configure getting HOTP codes through special key double press Signed-off-by: Szczepan Zalega --- unittest/test_pro.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/unittest/test_pro.py b/unittest/test_pro.py index b8109f2..aaf884f 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -631,6 +631,25 @@ def test_HOTP_secrets(C, secret): 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, 'double' + str(slot_number), secret, counter, use_8_digits, False, False, "", + DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + # requires manual check + def test_edit_OTP_slot(C): """ should change slots counter and name without changing its secret (using null secret for second update) -- cgit v1.2.1 From 25118d2dea54ce8c6eaec56d722628d0ef484e1c Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Sat, 19 Nov 2016 14:21:23 +0100 Subject: Merge HOTP and TOTP writing commands for 0.8 Signed-off-by: Szczepan Zalega --- NitrokeyManager.cc | 59 ++++++++++++----------------------------------- include/NitrokeyManager.h | 11 ++++----- 2 files changed, 19 insertions(+), 51 deletions(-) diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index b991af1..b130f4f 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -223,47 +223,17 @@ namespace nitrokey{ 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); + int internal_slot_number = get_internal_slot_number_for_hotp(slot_number); if (is_authorization_command_supported()){ - write_HOTP_slot_authorize(slot_number, slot_name, secret, hotp_counter, use_8_digits, use_enter, use_tokenID, + 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_HOTP_slot_no_authorize(slot_number, slot_name, secret, hotp_counter, use_8_digits, use_enter, use_tokenID, - token_ID, temporary_password); + 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_no_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) const { - auto payload2 = get_payload(); - strcpyT(payload2.temporary_admin_password, temporary_password); - strcpyT(payload2.data, slot_name); - payload2.length = strlen((const char *) payload2.data); - payload2.setTypeName(); - stick10_08::SendOTPData::CommandTransaction::run(*device, payload2); - - payload2 = get_payload(); - strcpyT(payload2.temporary_admin_password, temporary_password); - auto secret_bin = misc::hex_string_to_byte(secret); - vector_copy(payload2.data, secret_bin); - payload2.length = strlen((const char *) payload2.data); - payload2.setTypeSecret(); - stick10_08::SendOTPData::CommandTransaction::run(*device, payload2); - - auto payload = get_payload(); - 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 = hotp_counter; - payload.slot_number = slot_number; - stick10_08::WriteToOTPSlot::CommandTransaction::run(*device, payload); - } - 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) { @@ -302,23 +272,24 @@ namespace nitrokey{ 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); - slot_number = get_internal_slot_number_for_totp(slot_number); + int internal_slot_number = get_internal_slot_number_for_totp(slot_number); if (is_authorization_command_supported()){ - write_TOTP_slot_authorize(slot_number, slot_name, secret, time_window, use_8_digits, use_enter, use_tokenID, + 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_TOTP_slot_no_authorize(slot_number, slot_name, secret, time_window, use_8_digits, use_enter, use_tokenID, - token_ID, temporary_password); + 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_TOTP_slot_no_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) const { + 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(); strcpyT(payload2.temporary_admin_password, temporary_password); @@ -347,8 +318,8 @@ namespace nitrokey{ payload.use_8_digits = use_8_digits; payload.use_enter = use_enter; payload.use_tokenID = use_tokenID; - payload.slot_counter_or_interval = time_window; - payload.slot_number = slot_number; + payload.slot_counter_or_interval = counter_or_interval; + payload.slot_number = internal_slot_number; stick10_08::WriteToOTPSlot::CommandTransaction::run(*device, payload); } diff --git a/include/NitrokeyManager.h b/include/NitrokeyManager.h index c0064f7..14fa1e5 100644 --- a/include/NitrokeyManager.h +++ b/include/NitrokeyManager.h @@ -137,17 +137,14 @@ namespace nitrokey { bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, const char *temporary_password); - void write_HOTP_slot_no_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) const; - 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_TOTP_slot_no_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) const; + 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; }; } -- cgit v1.2.1 From 5adc4b754de0a55f8c92dfbcd868630e65b4781f Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Sat, 19 Nov 2016 14:36:02 +0100 Subject: Update description for TOTP_RFC_usepin test Signed-off-by: Szczepan Zalega --- unittest/test_pro.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/unittest/test_pro.py b/unittest/test_pro.py index aaf884f..c8be0e8 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -327,9 +327,12 @@ def test_TOTP_64bit_time(C): assert dev_res == lib_res -@pytest.mark.xfail(reason="NK Pro: possible firmware bug or communication issue: set time command not always changes the time on stick thus failing this test, " - "this does not influence normal use since setting time is not done every TOTP code request" - "Rarely fail occurs on NK Storage") +@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 @@ -350,8 +353,8 @@ def test_TOTP_RFC_usepin(C, PIN_protection): # Mode: Sha1, time step X=30 test_data = [ #Time T (hex) TOTP - (59, 0x1, 94287082), - (1111111109, 0x00000000023523EC, 7081804), + (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), -- cgit v1.2.1 From 9e0c85241234ae607c7eea4bb9f3ee61762b5c0c Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Sat, 26 Nov 2016 15:40:46 +0100 Subject: Update comments Signed-off-by: Szczepan Zalega --- include/stick10_commands_0.8.h | 2 +- unittest/constants.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/stick10_commands_0.8.h b/include/stick10_commands_0.8.h index f905794..7de2177 100644 --- a/include/stick10_commands_0.8.h +++ b/include/stick10_commands_0.8.h @@ -49,7 +49,7 @@ namespace nitrokey { public: struct CommandPayload { uint8_t temporary_admin_password[25]; - uint8_t type; //0-secret, 1-name + 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 diff --git a/unittest/constants.py b/unittest/constants.py index 3c19a9b..0897b42 100644 --- a/unittest/constants.py +++ b/unittest/constants.py @@ -2,7 +2,7 @@ from enum import Enum from misc import to_hex RFC_SECRET_HR = '12345678901234567890' -RFC_SECRET = to_hex(RFC_SECRET_HR) # '3031323334353637383930...' +RFC_SECRET = to_hex(RFC_SECRET_HR) # '31323334353637383930...' # print( repr((RFC_SECRET, RFC_SECRET_, len(RFC_SECRET))) ) -- cgit v1.2.1 From b9f7d118ecdef61764c3256a203010831e0c5d7d Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Sat, 26 Nov 2016 15:41:34 +0100 Subject: Test for manual checking of TOTP slot written by Nitrokey App Signed-off-by: Szczepan Zalega --- unittest/test_pro.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/unittest/test_pro.py b/unittest/test_pro.py index c8be0e8..89a6ccf 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -683,3 +683,23 @@ def test_edit_OTP_slot(C): assert gs(C.NK_get_hotp_slot_name(slot_number)) == changed_name +@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 = str(C.NK_get_totp_code(slot_number, 0, 0, period)) + code_device = '0'+code_device if len(code_device) < 6 else code_device + + oath = pytest.importorskip("oath") + lib_at = lambda : oath.totp(secret, period=period) + print (lib_at()) + assert lib_at() == code_device -- cgit v1.2.1 From f4b1f29058f55a716cb6e4e8a4f9bf0e6c7332fe Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Sat, 26 Nov 2016 15:42:48 +0100 Subject: Add missing newlines to make code format consistent Signed-off-by: Szczepan Zalega --- unittest/test_pro.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/unittest/test_pro.py b/unittest/test_pro.py index 89a6ccf..5b39c34 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -4,6 +4,7 @@ from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET from misc import ffi, gs, wait, cast_pointer_to_tuple from misc import is_pro_rtm_07, is_pro_rtm_08, is_storage + def test_enable_password_safe(C): assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK assert C.NK_enable_password_safe('wrong_password') == DeviceErrorCode.WRONG_PASSWORD @@ -252,6 +253,7 @@ def test_HOTP_token(C): assert hotp_code != 0 assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK + def test_HOTP_counters(C): """ # https://tools.ietf.org/html/rfc4226#page-32 @@ -374,6 +376,7 @@ def test_TOTP_RFC_usepin(C, PIN_protection): correct += expected_code == code_from_device assert data == responses or correct == len(test_data) + def test_get_slot_names(C): C.NK_set_debug(True) assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK @@ -502,6 +505,7 @@ def test_get_serial_number(C): assert len(sn) > 0 print(('Serial number of the device: ', sn)) + @pytest.mark.parametrize("secret", ['000001', '00'*10+'ff', '00'*19+'ff', '000102', '002EF43F51AFA97BA2B46418768123C9E1809A5B' ]) def test_OTP_secret_started_from_null(C, secret): ''' @@ -577,6 +581,7 @@ def test_TOTP_slots_read_write_at_time_period(C, time, period): lib_res += (time, lib_at(time)) assert dev_res == lib_res + @pytest.mark.parametrize("secret", [RFC_SECRET, 2*RFC_SECRET, '12'*10, '12'*30] ) def test_TOTP_secrets(C, secret): ''' @@ -606,6 +611,7 @@ def test_TOTP_secrets(C, secret): lib_res += (time, lib_at(time)) assert dev_res == lib_res + @pytest.mark.parametrize("secret", [RFC_SECRET, 2*RFC_SECRET, '12'*10, '12'*30] ) def test_HOTP_secrets(C, secret): ''' @@ -653,6 +659,7 @@ def test_special_double_press(C): DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK # requires manual check + def test_edit_OTP_slot(C): """ should change slots counter and name without changing its secret (using null secret for second update) -- cgit v1.2.1 From 740b85c7f935029003e205dcbb5d49842eac1ad6 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Tue, 6 Dec 2016 19:56:14 +0100 Subject: Get major firmware version --- NK_C_API.cc | 7 +++++++ NK_C_API.h | 6 +++++- NitrokeyManager.cc | 16 ++++++++++++++++ include/NitrokeyManager.h | 3 +++ include/stick20_commands.h | 10 ++++++++++ 5 files changed, 41 insertions(+), 1 deletion(-) diff --git a/NK_C_API.cc b/NK_C_API.cc index d42840b..e513a3b 100644 --- a/NK_C_API.cc +++ b/NK_C_API.cc @@ -471,6 +471,13 @@ extern int NK_get_progress_bar_value() { }); } +extern int NK_get_major_firmware_version(){ + auto m = NitrokeyManager::instance(); + return get_with_result([&](){ + return m->get_major_firmware_version(); + }); +} + } diff --git a/NK_C_API.h b/NK_C_API.h index a446a62..7f01900 100644 --- a/NK_C_API.h +++ b/NK_C_API.h @@ -324,7 +324,11 @@ extern int NK_erase_password_safe_slot(uint8_t slot_number); */ extern int NK_is_AES_supported(const char *user_password); - +/** + * Get device's major firmware version + * @return 7,8 for Pro and major for Storage + */ +extern int NK_get_major_firmware_version(); diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index b130f4f..da31c8d 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -4,6 +4,7 @@ #include "include/LibraryException.h" #include #include +#include #include "include/misc.h" namespace nitrokey{ @@ -589,9 +590,24 @@ namespace nitrokey{ {DeviceModel::STORAGE, 43}, }); auto status_p = GetStatus::CommandTransaction::run(*device); + //FIXME use different function for checking storage firmware version return status_p.data().firmware_version <= m[device->get_device_model()]; } + int NitrokeyManager::get_major_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.major; + } + } + return 0; + } + bool NitrokeyManager::is_AES_supported(const char *user_password) { auto a = get_payload(); strcpyT(a.user_password, user_password); diff --git a/include/NitrokeyManager.h b/include/NitrokeyManager.h index 14fa1e5..fd39445 100644 --- a/include/NitrokeyManager.h +++ b/include/NitrokeyManager.h @@ -115,6 +115,8 @@ namespace nitrokey { template void authorize_packet(T &package, const char *admin_temporary_password, shared_ptr device); + int get_major_firmware_version(); + private: NitrokeyManager(); @@ -145,6 +147,7 @@ namespace nitrokey { uint64_t counter_or_interval, bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, const char *temporary_password) const; + }; } diff --git a/include/stick20_commands.h b/include/stick20_commands.h index 1af9da3..e6df770 100644 --- a/include/stick20_commands.h +++ b/include/stick20_commands.h @@ -125,7 +125,17 @@ namespace nitrokey { uint16_t MagicNumber_StickConfig_u16; uint8_t ReadWriteFlagUncryptedVolume_u8; uint8_t ReadWriteFlagCryptedVolume_u8; + + union{ uint8_t VersionInfo_au8[4]; + struct { + uint8_t __unused; + uint8_t major; + uint8_t __unused2; + uint8_t minor; + } __packed versionInfo; + }; + uint8_t ReadWriteFlagHiddenVolume_u8; uint8_t FirmwareLocked_u8; uint8_t NewSDCardFound_u8; -- cgit v1.2.1 From 279a310d6710908943237f5528d64a94ecd45885 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Tue, 6 Dec 2016 20:00:42 +0100 Subject: Tests: check are long OTP secrets supported Signed-off-by: Szczepan Zalega --- unittest/misc.py | 24 ++++++++++++++---------- unittest/test_library.py | 4 +++- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/unittest/misc.py b/unittest/misc.py index 8296814..f4d7731 100644 --- a/unittest/misc.py +++ b/unittest/misc.py @@ -20,25 +20,29 @@ def cast_pointer_to_tuple(obj, typen, len): # config = cast_pointer_to_tuple(config_raw_data, 'uint8_t', 5) return tuple(ffi.cast("%s [%d]" % (typen, len), obj)[0:len]) -def get_firmware_version_from_status(C): - status = gs(C.NK_status()) - status = [s if 'firmware_version' in s else '' for s in status.split('\n')] - firmware = status[0].split(':')[1] + +def get_devices_firmware_version(C): + firmware = C.NK_get_major_firmware_version() return firmware def is_pro_rtm_07(C): - firmware = get_firmware_version_from_status(C) - return '07 00' in firmware + firmware = get_devices_firmware_version(C) + return firmware == 7 + def is_pro_rtm_08(C): - firmware = get_firmware_version_from_status(C) - return '08 00' in firmware + firmware = get_devices_firmware_version(C) + return firmware == 8 def is_storage(C): """ exact firmware storage is sent by other function """ - firmware = get_firmware_version_from_status(C) - return '01 00' in firmware \ No newline at end of file + # 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/test_library.py b/unittest/test_library.py index d0eef80..7b05c58 100644 --- a/unittest/test_library.py +++ b/unittest/test_library.py @@ -1,6 +1,6 @@ import pytest -from misc import ffi, gs, to_hex +from misc import ffi, gs, to_hex, is_pro_rtm_07, is_long_OTP_secret_handled from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET, LibraryErrors def test_too_long_strings(C): @@ -50,6 +50,8 @@ def test_invalid_secret_hex_string_for_OTP_write(C, invalid_hex_string): def test_warning_binary_bigger_than_secret_buffer(C): invalid_hex_string = to_hex('1234567890') * 3 + if is_long_OTP_secret_handled(C): + invalid_hex_string *= 2 assert C.NK_write_hotp_slot(1, 'slot_name', invalid_hex_string, 0, True, False, False, '', DefaultPasswords.ADMIN_TEMP) == LibraryErrors.TARGET_BUFFER_SIZE_SMALLER_THAN_SOURCE -- cgit v1.2.1 From 4ab91aae6c101b72a94d3785dbdad117354b87d5 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Tue, 6 Dec 2016 20:22:37 +0100 Subject: Check maximum OTP secret size in new authorization style commands Authenticate before testing invalid hex strings Remove invalid test for empty string for writing otp slot (empty string allows editing) Signed-off-by: Szczepan Zalega --- NitrokeyManager.cc | 4 ++++ unittest/test_library.py | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index da31c8d..a15b9c8 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -302,6 +302,10 @@ namespace nitrokey{ 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); diff --git a/unittest/test_library.py b/unittest/test_library.py index 7b05c58..bd44e89 100644 --- a/unittest/test_library.py +++ b/unittest/test_library.py @@ -36,12 +36,13 @@ def test_invalid_slot(C): 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]) + ['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. """ + assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK assert C.NK_write_hotp_slot(1, 'slot_name', invalid_hex_string, 0, True, False, False, '', DefaultPasswords.ADMIN_TEMP) == LibraryErrors.INVALID_HEX_STRING assert C.NK_write_totp_slot(1, 'python_test', invalid_hex_string, 30, True, False, False, "", -- cgit v1.2.1 From eaab841afe29334e5a599b2210f2e8910b0d0b71 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 9 Dec 2016 11:06:06 +0100 Subject: Use switch to translate command and device statuses instead of array Signed-off-by: Szczepan Zalega --- include/dissect.h | 59 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/include/dissect.h b/include/dissect.h index 59e6e9c..8c975c5 100644 --- a/include/dissect.h +++ b/include/dissect.h @@ -36,44 +36,65 @@ class QueryDissector : semantics::non_constructible { } }; + + + template class ResponseDissector : semantics::non_constructible { public: + static std::string status_translate_device(int status){ + auto enum_status = static_cast(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(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) - std::string status[4]; - status[0] = " STATUS_READY"; - status[1] = " STATUS_BUSY"; - status[2] = " STATUS_ERROR"; - status[3] = " STATUS_RECEIVED_REPORT"; - std::string cmd[11]; - cmd[0] = " CMD_STATUS_OK"; - cmd[1] = " CMD_STATUS_WRONG_CRC"; - cmd[2] = " CMD_STATUS_WRONG_SLOT"; - cmd[3] = " CMD_STATUS_SLOT_NOT_PROGRAMMED"; - cmd[4] = " CMD_STATUS_WRONG_PASSWORD"; - cmd[5] = " CMD_STATUS_NOT_AUTHORIZED"; - cmd[6] = " CMD_STATUS_TIMESTAMP_WARNING"; - cmd[7] = " CMD_STATUS_NO_NAME_ERROR"; - cmd[8] = " CMD_STATUS_NOT_SUPPORTED"; - cmd[9] = " CMD_STATUS_UNKNOWN_COMMAND"; - cmd[10] = " CMD_STATUS_AES_DEC_FAILED"; out << "Raw HID packet:" << std::endl; out << ::nitrokey::misc::hexdump((const char *)(&pod), sizeof pod); out << "Device status:\t" << pod.device_status + 0 << " " - << status[pod.device_status] << std::endl; + << 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 << " " - << cmd[pod.last_command_status] << std::endl; + << status_translate_command(pod.last_command_status) << std::endl; out << "CRC:\t" << std::hex << std::setw(2) << std::setfill('0') << pod.crc << std::endl; -- cgit v1.2.1 From 60d744fec7d20fb93b5152f3a7db0101009831cb Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 9 Dec 2016 11:09:07 +0100 Subject: Allow to skip test if device is not able to pass it Signed-off-by: Szczepan Zalega --- unittest/conftest.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/unittest/conftest.py b/unittest/conftest.py index 68227d5..88bf7d0 100644 --- a/unittest/conftest.py +++ b/unittest/conftest.py @@ -2,6 +2,16 @@ 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' @@ -24,7 +34,11 @@ def C(request): nk_login = C.NK_login_auto() if nk_login != 1: print('No devices detected!') - assert nk_login == 1 # returns 0 if not connected or wrong model or 1 when connected + 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 -- cgit v1.2.1 From e75f9a54e0a696de47f00dce980b1a3b9feddee2 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 9 Dec 2016 11:09:45 +0100 Subject: Code reformat in library test Signed-off-by: Szczepan Zalega --- unittest/test_library.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/unittest/test_library.py b/unittest/test_library.py index bd44e89..ec00a5a 100644 --- a/unittest/test_library.py +++ b/unittest/test_library.py @@ -3,6 +3,7 @@ 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 + def test_too_long_strings(C): new_password = '123123123' long_string = 'a' * 100 @@ -35,6 +36,7 @@ def test_invalid_slot(C): assert gs(C.NK_get_password_safe_slot_login(invalid_slot)) == '' 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): @@ -48,7 +50,6 @@ def test_invalid_secret_hex_string_for_OTP_write(C, invalid_hex_string): assert C.NK_write_totp_slot(1, 'python_test', invalid_hex_string, 30, True, False, False, "", DefaultPasswords.ADMIN_TEMP) == LibraryErrors.INVALID_HEX_STRING - def test_warning_binary_bigger_than_secret_buffer(C): invalid_hex_string = to_hex('1234567890') * 3 if is_long_OTP_secret_handled(C): -- cgit v1.2.1 From 8e1499871dda0559b0d7164e23d9e146f10409ec Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 9 Dec 2016 11:10:35 +0100 Subject: Apply firmware versions limits to tests Signed-off-by: Szczepan Zalega --- unittest/test_pro.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/unittest/test_pro.py b/unittest/test_pro.py index 5b39c34..7f567c7 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -1,5 +1,6 @@ import pytest +from conftest import skip_if_device_version_lower_than from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET from misc import ffi, gs, wait, cast_pointer_to_tuple from misc import is_pro_rtm_07, is_pro_rtm_08, is_storage @@ -511,6 +512,8 @@ 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}) + oath = pytest.importorskip("oath") lib_at = lambda t: oath.hotp(secret, t, format='dec6') PIN_protection = False @@ -532,8 +535,18 @@ def test_OTP_secret_started_from_null(C, secret): assert dev_res == lib_res -@pytest.mark.parametrize("counter", [0, 3, 7, 0xffff, 0xffffffff, 0xffffffffffffffff] ) +@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: oath.hotp(secret, t, format='dec6') @@ -587,6 +600,8 @@ 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 is_pro_rtm_07(C) and 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 ") slot_number = 0 @@ -617,6 +632,8 @@ def test_HOTP_secrets(C, secret): ''' NK Pro 0.8+, NK Storage 0.44+ ''' + skip_if_device_version_lower_than({'S': 44, 'P': 8}) + if is_pro_rtm_07(C) and 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 ") slot_number = 0 -- cgit v1.2.1 From 7c432494269144fa9777266834fd5b88b4fe1b90 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 9 Dec 2016 11:12:24 +0100 Subject: Add NK Storage specific reply on running not initialized Password Safe Signed-off-by: Szczepan Zalega --- unittest/test_pro.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/unittest/test_pro.py b/unittest/test_pro.py index 7f567c7..6b47c9f 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -81,7 +81,9 @@ def test_enable_password_safe_after_factory_reset(C): assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK assert C.NK_factory_reset(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK wait(10) - assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_AES_DEC_FAILED + 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 -- cgit v1.2.1 From b78c28cb133bd00024416d8ad69740040c91d589 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 9 Dec 2016 11:13:20 +0100 Subject: Enable factory reset test for Nitrokey Storage. Comments. Signed-off-by: Szczepan Zalega --- unittest/test_pro.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/unittest/test_pro.py b/unittest/test_pro.py index 6b47c9f..3282436 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -476,8 +476,6 @@ def test_read_write_config(C): def test_factory_reset(C): - if is_storage(C): - pytest.skip('Recovery not implemented for NK Storage') C.NK_set_debug(True) 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 @@ -494,6 +492,8 @@ def test_factory_reset(C): 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): + C.NK_clear_new_sd_card_warning(DefaultPasswords.ADMIN) def test_get_status(C): @@ -573,6 +573,10 @@ def test_HOTP_slots_read_write_counter(C, counter): @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: oath.totp(RFC_SECRET, t=t, period=period) -- cgit v1.2.1 From e513f430851ad9645aff53df32813a5343c697c1 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 9 Dec 2016 12:43:24 +0100 Subject: Skip non-Storage and old devices for Storage tests Signed-off-by: Szczepan Zalega --- unittest/test_storage.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/unittest/test_storage.py b/unittest/test_storage.py index 01276ce..a1c59aa 100644 --- a/unittest/test_storage.py +++ b/unittest/test_storage.py @@ -1,9 +1,10 @@ -import pytest +import pprint -from misc import ffi, gs, wait, cast_pointer_to_tuple -from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET, LibraryErrors +import pytest -import pprint +from conftest import skip_if_device_version_lower_than +from constants import DefaultPasswords, DeviceErrorCode +from misc import gs, wait pprint = pprint.PrettyPrinter(indent=4).pprint @@ -22,6 +23,7 @@ def get_dict_from_dissect(status): 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) @@ -32,6 +34,7 @@ def test_get_status_storage(C): 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) @@ -41,11 +44,13 @@ def test_sd_card_usage(C): 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 def test_encrypted_volume_unlock_hidden(C): + skip_if_device_version_lower_than({'S': 43}) hidden_volume_password = 'hiddenpassword' assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK @@ -54,6 +59,7 @@ def test_encrypted_volume_unlock_hidden(C): @pytest.mark.skip(reason='hangs device, to report') def test_encrypted_volume_setup_multiple_hidden(C): + skip_if_device_version_lower_than({'S': 43}) hidden_volume_password = 'hiddenpassword' p = lambda i: hidden_volume_password + str(i) assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK @@ -67,25 +73,30 @@ def test_encrypted_volume_setup_multiple_hidden(C): 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 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 def test_export_firmware(C): + skip_if_device_version_lower_than({'S': 43}) assert C.NK_export_firmware(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK 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.skip 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: @@ -97,12 +108,14 @@ def test_fill_SD_card(C): 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 def test_change_update_password(C): + skip_if_device_version_lower_than({'S': 43}) wrong_password = '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 @@ -110,5 +123,6 @@ def test_change_update_password(C): 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 -- cgit v1.2.1 From 87eaf78c7c7290764ccaebe67726b77a44f21240 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 9 Dec 2016 12:44:33 +0100 Subject: Remove old skipping code. Feature comment. Signed-off-by: Szczepan Zalega --- unittest/test_library.py | 5 ----- unittest/test_pro.py | 7 +++---- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/unittest/test_library.py b/unittest/test_library.py index ec00a5a..b24c72a 100644 --- a/unittest/test_library.py +++ b/unittest/test_library.py @@ -58,11 +58,6 @@ def test_warning_binary_bigger_than_secret_buffer(C): DefaultPasswords.ADMIN_TEMP) == LibraryErrors.TARGET_BUFFER_SIZE_SMALLER_THAN_SOURCE -@pytest.mark.xfail(reason="TODO") -def test_OTP_secret_started_from_null(C): - assert False - - @pytest.mark.skip(reason='Experimental') def test_clear(C): d = 'asdasdasd' diff --git a/unittest/test_pro.py b/unittest/test_pro.py index 3282436..ab30e04 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -635,13 +635,12 @@ def test_TOTP_secrets(C, secret): @pytest.mark.parametrize("secret", [RFC_SECRET, 2*RFC_SECRET, '12'*10, '12'*30] ) def test_HOTP_secrets(C, secret): - ''' + """ NK Pro 0.8+, NK Storage 0.44+ - ''' + feature needed: support for 320bit secrets + """ skip_if_device_version_lower_than({'S': 44, 'P': 8}) - if is_pro_rtm_07(C) and 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 ") slot_number = 0 counter = 0 oath = pytest.importorskip("oath") -- cgit v1.2.1 From 1d493a5daba996e31615154b28688de3637529c7 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 9 Dec 2016 13:46:54 +0100 Subject: Test null started OTP secrets also for 320bit case Signed-off-by: Szczepan Zalega --- unittest/test_pro.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/unittest/test_pro.py b/unittest/test_pro.py index ab30e04..d2ed48b 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -509,12 +509,16 @@ def test_get_serial_number(C): print(('Serial number of the device: ', sn)) -@pytest.mark.parametrize("secret", ['000001', '00'*10+'ff', '00'*19+'ff', '000102', '002EF43F51AFA97BA2B46418768123C9E1809A5B' ]) +@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({'S': 44, 'P': 8}) oath = pytest.importorskip("oath") lib_at = lambda t: oath.hotp(secret, t, format='dec6') -- cgit v1.2.1 From e414840ab19ce022d5354fad13bcbcbe41925eea Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Fri, 9 Dec 2016 13:49:33 +0100 Subject: Add a note regarding Password Safe tests Signed-off-by: Szczepan Zalega --- unittest/test_pro.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/unittest/test_pro.py b/unittest/test_pro.py index d2ed48b..2a4a1a7 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -7,6 +7,9 @@ from misc import is_pro_rtm_07, is_pro_rtm_08, is_storage 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('wrong_password') == DeviceErrorCode.WRONG_PASSWORD assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK -- cgit v1.2.1 From 925d75007161aa845e8c29ac412ac875cf45f337 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Mon, 12 Dec 2016 15:10:09 +0100 Subject: Tests: clear new SD card warning on factory reset Signed-off-by: Szczepan Zalega --- unittest/test_pro.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/unittest/test_pro.py b/unittest/test_pro.py index 2a4a1a7..a6f7094 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -84,6 +84,8 @@ def test_enable_password_safe_after_factory_reset(C): assert C.NK_lock_device() == 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 @@ -496,7 +498,7 @@ def test_factory_reset(C): assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK if is_storage(C): - C.NK_clear_new_sd_card_warning(DefaultPasswords.ADMIN) + assert C.NK_clear_new_sd_card_warning(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK def test_get_status(C): -- cgit v1.2.1 From 5e02e1e198c1c4051f15c45dfeb67b9be2cd5aa1 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Mon, 12 Dec 2016 15:10:55 +0100 Subject: Tests: skip edit OTP slot test for NK Storage 0.44 Signed-off-by: Szczepan Zalega --- unittest/test_pro.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/unittest/test_pro.py b/unittest/test_pro.py index a6f7094..4a2a504 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -695,6 +695,9 @@ def test_edit_OTP_slot(C): """ should change slots counter and name without changing its secret (using null secret for second update) """ + # counter does not reset under Storage v0.43 + skip_if_device_version_lower_than({'S': 44, 'P': 7}) + secret = RFC_SECRET counter = 0 PIN_protection = False -- cgit v1.2.1 From 58c15291c3c758bc79adc8e65d471b1f5dded136 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Mon, 12 Dec 2016 15:22:11 +0100 Subject: Remove changes to Pro 0.7 stick commands Signed-off-by: Szczepan Zalega --- include/stick10_commands.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/stick10_commands.h b/include/stick10_commands.h index 5ae2591..f02fd70 100644 --- a/include/stick10_commands.h +++ b/include/stick10_commands.h @@ -48,7 +48,6 @@ class EraseSlot : Command { public: struct CommandPayload { uint8_t slot_number; - uint8_t temporary_admin_password[25]; bool isValid() const { return !(slot_number & 0xF0); } std::string dissect() const { @@ -138,7 +137,6 @@ class WriteToHOTPSlot : Command { }; class WriteToTOTPSlot : Command { - //admin auth public: struct CommandPayload { uint8_t slot_number; @@ -184,7 +182,6 @@ class WriteToTOTPSlot : Command { }; class GetTOTP : Command { - //user auth public: struct CommandPayload { uint8_t slot_number; @@ -615,7 +612,6 @@ class PasswordSafeSendSlotViaHID : Command { // TODO "Device::passwordSafeSendSlotDataViaHID" class WriteGeneralConfig : Command { - //admin auth public: struct CommandPayload { union{ -- cgit v1.2.1 From 9c627a75f027e2a6a9518869dc48fba28904ba49 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Mon, 12 Dec 2016 15:36:55 +0100 Subject: Use proper firmware version getter for feature availability check Signed-off-by: Szczepan Zalega --- NitrokeyManager.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index a15b9c8..ddec600 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -589,13 +589,12 @@ namespace nitrokey{ } bool NitrokeyManager::is_authorization_command_supported(){ + //authorization command is supported for versions equal or below: auto m = std::unordered_map({ {DeviceModel::PRO, 7}, {DeviceModel::STORAGE, 43}, }); - auto status_p = GetStatus::CommandTransaction::run(*device); - //FIXME use different function for checking storage firmware version - return status_p.data().firmware_version <= m[device->get_device_model()]; + return get_major_firmware_version() <= m[device->get_device_model()]; } int NitrokeyManager::get_major_firmware_version(){ -- cgit v1.2.1 From e26c6da38c674d8ec37e402132dab823bd22bd36 Mon Sep 17 00:00:00 2001 From: Szczepan Zalega Date: Mon, 12 Dec 2016 15:48:38 +0100 Subject: Use the rest of the Pro 0.7 commands in 0.8 interface Signed-off-by: Szczepan Zalega --- include/stick10_commands_0.8.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/include/stick10_commands_0.8.h b/include/stick10_commands_0.8.h index 7de2177..9594d1e 100644 --- a/include/stick10_commands_0.8.h +++ b/include/stick10_commands_0.8.h @@ -25,6 +25,27 @@ namespace nitrokey { 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 { public: -- cgit v1.2.1