diff options
-rw-r--r-- | NK_C_API.cc | 76 | ||||
-rw-r--r-- | NK_C_API.h | 43 | ||||
-rw-r--r-- | NitrokeyManager.cc | 38 | ||||
-rw-r--r-- | device.cc | 5 | ||||
-rw-r--r-- | include/NitrokeyManager.h | 16 | ||||
-rw-r--r-- | include/device.h | 2 | ||||
-rw-r--r-- | include/device_proto.h | 5 | ||||
-rw-r--r-- | include/stick10_commands.h | 126 | ||||
-rw-r--r-- | unittest/test_bindings.py | 230 |
9 files changed, 382 insertions, 159 deletions
diff --git a/NK_C_API.cc b/NK_C_API.cc index 81a18b5..d2aa38a 100644 --- a/NK_C_API.cc +++ b/NK_C_API.cc @@ -13,9 +13,9 @@ T* array_dup(std::vector<T>& v){ template <typename T> uint8_t * get_with_array_result(T func){ + NK_last_command_status = 0; try { return func(); - NK_last_command_status = 0; } catch (CommandFailedException & commandFailedException){ NK_last_command_status = commandFailedException.last_command_status; @@ -25,9 +25,9 @@ uint8_t * get_with_array_result(T func){ template <typename T> const char* get_with_string_result(T func){ + NK_last_command_status = 0; try { return func(); - NK_last_command_status = 0; } catch (CommandFailedException & commandFailedException){ NK_last_command_status = commandFailedException.last_command_status; @@ -37,21 +37,22 @@ const char* get_with_string_result(T func){ template <typename T> auto get_with_result(T func){ + NK_last_command_status = 0; try { return func(); - NK_last_command_status = 0; } catch (CommandFailedException & commandFailedException){ NK_last_command_status = commandFailedException.last_command_status; return commandFailedException.last_command_status; +// return (uint8_t) 0; } } template <typename T> uint8_t get_without_result(T func){ + NK_last_command_status = 0; try { func(); - NK_last_command_status = 0; return 0; } catch (CommandFailedException & commandFailedException){ @@ -71,8 +72,8 @@ extern uint8_t NK_get_last_command_status(){ extern int NK_login(const char *device_model) { auto m = NitrokeyManager::instance(); try { - m->connect(device_model); NK_last_command_status = 0; + m->connect(device_model); } catch (CommandFailedException & commandFailedException){ NK_last_command_status = commandFailedException.last_command_status; @@ -84,8 +85,8 @@ extern int NK_login(const char *device_model) { extern int NK_logout() { auto m = NitrokeyManager::instance(); try { - m->disconnect(); NK_last_command_status = 0; + m->disconnect(); } catch (CommandFailedException & commandFailedException){ NK_last_command_status = commandFailedException.last_command_status; @@ -105,31 +106,32 @@ extern int NK_first_authenticate(const char* admin_password, const char* admin_t extern int NK_user_authenticate(const char* user_password, const char* user_temporary_password){ auto m = NitrokeyManager::instance(); return get_without_result( [&](){ - return m->user_authenticate(user_password, user_temporary_password); + m->user_authenticate(user_password, user_temporary_password); }); } extern int NK_factory_reset(const char* admin_password){ auto m = NitrokeyManager::instance(); return get_without_result( [&](){ - return m->factory_reset(admin_password); + m->factory_reset(admin_password); }); } extern int NK_build_aes_key(const char* admin_password){ auto m = NitrokeyManager::instance(); return get_without_result( [&](){ - return m->build_aes_key(admin_password); + m->build_aes_key(admin_password); }); } -extern int NK_unlock_user_password(const char* admin_password){ +extern int NK_unlock_user_password(const char *admin_password, const char *new_user_password) { auto m = NitrokeyManager::instance(); return get_without_result( [&](){ - return m->unlock_user_password(admin_password); + m->unlock_user_password(admin_password, new_user_password); }); } -extern int NK_write_config(bool numlock, bool capslock, bool scrolllock, bool enable_user_password, bool delete_user_password, +extern int NK_write_config(uint8_t numlock, uint8_t capslock, uint8_t scrolllock, bool enable_user_password, + bool delete_user_password, const char *admin_temporary_password) { auto m = NitrokeyManager::instance(); return get_without_result( [&](){ @@ -148,10 +150,10 @@ extern uint8_t* NK_read_config(){ extern const char * NK_status() { + NK_last_command_status = 0; auto m = NitrokeyManager::instance(); try { string s = m->get_status(); - NK_last_command_status = 0; return strdup(s.c_str()); //FIXME leak? } catch (CommandFailedException & commandFailedException){ @@ -166,10 +168,10 @@ extern uint32_t NK_get_hotp_code(uint8_t slot_number) { } extern uint32_t NK_get_hotp_code_PIN(uint8_t slot_number, const char* user_temporary_password){ + NK_last_command_status = 0; auto m = NitrokeyManager::instance(); try { const auto code = m->get_HOTP_code(slot_number, user_temporary_password); - NK_last_command_status = 0; return code; } catch (CommandFailedException & commandFailedException){ @@ -185,10 +187,10 @@ extern uint32_t NK_get_totp_code(uint8_t slot_number, uint64_t challenge, uint64 extern uint32_t NK_get_totp_code_PIN(uint8_t slot_number, uint64_t challenge, uint64_t last_totp_time, uint8_t last_interval, const char* user_temporary_password){ + NK_last_command_status = 0; auto m = NitrokeyManager::instance(); try { const auto totp_code = m->get_TOTP_code(slot_number, challenge, last_totp_time, last_interval, user_temporary_password); - NK_last_command_status = 0; return totp_code; } catch (CommandFailedException & commandFailedException){ @@ -198,10 +200,10 @@ extern uint32_t NK_get_totp_code_PIN(uint8_t slot_number, uint64_t challenge, ui } extern int NK_erase_hotp_slot(uint8_t slot_number, const char *temporary_password) { + NK_last_command_status = 0; auto m = NitrokeyManager::instance(); try { m->erase_hotp_slot(slot_number, temporary_password); - NK_last_command_status = 0; } catch (CommandFailedException & commandFailedException){ NK_last_command_status = commandFailedException.last_command_status; @@ -211,10 +213,10 @@ extern int NK_erase_hotp_slot(uint8_t slot_number, const char *temporary_passwor } extern int NK_erase_totp_slot(uint8_t slot_number, const char *temporary_password) { + NK_last_command_status = 0; auto m = NitrokeyManager::instance(); try { m->erase_totp_slot(slot_number, temporary_password); - NK_last_command_status = 0; } catch (CommandFailedException & commandFailedException){ NK_last_command_status = commandFailedException.last_command_status; @@ -224,11 +226,13 @@ extern int NK_erase_totp_slot(uint8_t slot_number, const char *temporary_passwor } extern int NK_write_hotp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint8_t hotp_counter, - bool use_8_digits, const char *temporary_password) { + bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, + const char *temporary_password) { + NK_last_command_status = 0; auto m = NitrokeyManager::instance(); try { - m->write_HOTP_slot(slot_number, slot_name, secret, hotp_counter, use_8_digits, temporary_password); - NK_last_command_status = 0; + m->write_HOTP_slot(slot_number, slot_name, secret, hotp_counter, use_8_digits, use_enter, use_tokenID, token_ID, + temporary_password); } catch (CommandFailedException & commandFailedException){ NK_last_command_status = commandFailedException.last_command_status; @@ -238,11 +242,13 @@ extern int NK_write_hotp_slot(uint8_t slot_number, const char *slot_name, const } extern int NK_write_totp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window, - bool use_8_digits, const char *temporary_password) { + bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, + const char *temporary_password) { + NK_last_command_status = 0; auto m = NitrokeyManager::instance(); try { - m->write_TOTP_slot(slot_number, slot_name, secret, time_window, use_8_digits, temporary_password); - NK_last_command_status = 0; + m->write_TOTP_slot(slot_number, slot_name, secret, time_window, use_8_digits, use_enter, use_tokenID, token_ID, + temporary_password); } catch (CommandFailedException & commandFailedException){ NK_last_command_status = commandFailedException.last_command_status; @@ -252,10 +258,10 @@ extern int NK_write_totp_slot(uint8_t slot_number, const char *slot_name, const } extern const char* NK_get_totp_slot_name(uint8_t slot_number){ + NK_last_command_status = 0; auto m = NitrokeyManager::instance(); try { const auto slot_name = m->get_totp_slot_name(slot_number); - NK_last_command_status = 0; return slot_name; } catch (CommandFailedException & commandFailedException){ @@ -264,10 +270,10 @@ extern const char* NK_get_totp_slot_name(uint8_t slot_number){ } } extern const char* NK_get_hotp_slot_name(uint8_t slot_number){ + NK_last_command_status = 0; auto m = NitrokeyManager::instance(); try { const auto slot_name = m->get_hotp_slot_name(slot_number); - NK_last_command_status = 0; return slot_name; } catch (CommandFailedException & commandFailedException){ @@ -282,10 +288,10 @@ extern void NK_set_debug(bool state){ } extern int NK_totp_set_time(uint64_t time){ + NK_last_command_status = 0; auto m = NitrokeyManager::instance(); try { m->set_time(time); - NK_last_command_status = 0; } catch (CommandFailedException & commandFailedException){ NK_last_command_status = commandFailedException.last_command_status; @@ -295,10 +301,10 @@ extern int NK_totp_set_time(uint64_t time){ } extern int NK_totp_get_time(){ + NK_last_command_status = 0; auto m = NitrokeyManager::instance(); try { m->get_time(); - NK_last_command_status = 0; } catch (CommandFailedException & commandFailedException){ NK_last_command_status = commandFailedException.last_command_status; @@ -308,10 +314,10 @@ extern int NK_totp_get_time(){ } extern int NK_change_admin_PIN(char *current_PIN, char *new_PIN){ + NK_last_command_status = 0; auto m = NitrokeyManager::instance(); try { m->change_admin_PIN(current_PIN, new_PIN); - NK_last_command_status = 0; } catch (CommandFailedException & commandFailedException){ NK_last_command_status = commandFailedException.last_command_status; @@ -321,10 +327,10 @@ extern int NK_change_admin_PIN(char *current_PIN, char *new_PIN){ } extern int NK_change_user_PIN(char *current_PIN, char *new_PIN){ + NK_last_command_status = 0; auto m = NitrokeyManager::instance(); try { m->change_user_PIN(current_PIN, new_PIN); - NK_last_command_status = 0; } catch (CommandFailedException & commandFailedException){ NK_last_command_status = commandFailedException.last_command_status; @@ -334,10 +340,10 @@ extern int NK_change_user_PIN(char *current_PIN, char *new_PIN){ } extern int NK_enable_password_safe(const char *user_pin){ + NK_last_command_status = 0; auto m = NitrokeyManager::instance(); try { m->enable_password_safe(user_pin); - NK_last_command_status = 0; } catch (CommandFailedException & commandFailedException){ NK_last_command_status = commandFailedException.last_command_status; @@ -346,12 +352,12 @@ extern int NK_enable_password_safe(const char *user_pin){ return 0; } extern uint8_t * NK_get_password_safe_slot_status(){ + NK_last_command_status = 0; auto m = NitrokeyManager::instance(); auto res = new uint8_t[16]; memset(res, 0, 16); try { const auto slot_status = m->get_password_safe_slot_status(); - NK_last_command_status = 0; return slot_status; //TODO FIXME } catch (CommandFailedException & commandFailedException){ @@ -415,5 +421,13 @@ extern int NK_erase_password_safe_slot(uint8_t slot_number) { }); } +extern int NK_is_AES_supported(const char *user_password) { + auto m = NitrokeyManager::instance(); + return get_with_result([&](){ + return (uint8_t) m->is_AES_supported(user_password); + }); +} + + } @@ -8,8 +8,6 @@ extern "C" { -//Make sure each function's declaration is in one line (for automatic python declaration processing) - /** * Set debug level of messages written on stderr * @param state state=True - all messages, state=False - only errors level @@ -83,22 +81,25 @@ extern int NK_build_aes_key(const char* admin_password); * @param admin_password char[20](Pro) current administrator PIN * @return command processing error code */ -extern int NK_unlock_user_password(const char* admin_password); +extern int NK_unlock_user_password(const char *admin_password, const char *new_user_password); /** * Write general config to the device - * @param numlock set True to send OTP code after double pressing numlock - * @param capslock set True to send OTP code after double pressing capslock - * @param scrolllock set True to send OTP code after double pressing scrolllock + * @param numlock set value in range [0-1] to send HOTP code from slot 'numlock' after double pressing numlock + * or outside the range to disable this function + * @param capslock similar to numlock but with capslock + * @param scrolllock similar to numlock but with scrolllock * @param enable_user_password set True to enable OTP PIN protection (request PIN each OTP code request) * @param delete_user_password set True to disable OTP PIN protection (request PIN each OTP code request) * @param admin_temporary_password current admin temporary password * @return command processing error code */ -extern int NK_write_config(bool numlock, bool capslock, bool scrolllock, bool enable_user_password, bool delete_user_password, const char *admin_temporary_password); +extern int NK_write_config(uint8_t numlock, uint8_t capslock, uint8_t scrolllock, + bool enable_user_password, bool delete_user_password, const char *admin_temporary_password); /** - * Get currently set config - status of function Numlock/Capslock/Scrollock OTP sending and PIN protected OTP + * Get currently set config - status of function Numlock/Capslock/Scrollock OTP sending and is enabled PIN protected OTP + * @see NK_write_config * @return uint8_t general_config[5]: * uint8_t numlock; uint8_t capslock; @@ -148,10 +149,15 @@ extern int NK_erase_totp_slot(uint8_t slot_number, const char *temporary_passwor * @param secret char[20](Pro) 160-bit secret * @param hotp_counter uint32_t starting value of HOTP counter * @param use_8_digits should returned codes be 6 (false) or 8 digits (true) + * @param use_enter press ENTER key after sending OTP code using double-pressed scroll/num/capslock + * @param use_tokenID @see token_ID + * @param token_ID @see https://openauthentication.org/token-specs/, 'Class A' section * @param temporary_password char[25](Pro) admin temporary password * @return command processing error code */ -extern int NK_write_hotp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint8_t hotp_counter, bool use_8_digits, const char *temporary_password); +extern int NK_write_hotp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint8_t hotp_counter, + bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, + const char *temporary_password); /** * Write TOTP slot data to the device @@ -160,10 +166,15 @@ extern int NK_write_hotp_slot(uint8_t slot_number, const char *slot_name, const * @param secret char[20](Pro) 160-bit secret * @param time_window uint16_t time window for this TOTP * @param use_8_digits should returned codes be 6 (false) or 8 digits (true) + * @param use_enter press ENTER key after sending OTP code using double-pressed scroll/num/capslock + * @param use_tokenID @see token_ID + * @param token_ID @see https://openauthentication.org/token-specs/, 'Class A' section * @param temporary_password char[20](Pro) admin temporary password * @return command processing error code */ -extern int NK_write_totp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window, bool use_8_digits, const char *temporary_password); +extern int NK_write_totp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window, + bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, + const char *temporary_password); /** * Get HOTP code from the device @@ -201,7 +212,8 @@ extern uint32_t NK_get_totp_code(uint8_t slot_number, uint64_t challenge, uint64 * otherwise should be set to empty string - '' * @return TOTP code */ -extern uint32_t NK_get_totp_code_PIN(uint8_t slot_number, uint64_t challenge, uint64_t last_totp_time, uint8_t last_interval, const char* user_temporary_password); +extern uint32_t NK_get_totp_code_PIN(uint8_t slot_number, uint64_t challenge, + uint64_t last_totp_time, uint8_t last_interval, const char* user_temporary_password); /** * Set time on the device (for TOTP requests) @@ -284,7 +296,8 @@ extern const char *NK_get_password_safe_slot_password(uint8_t slot_number); * @param slot_password char[20](Pro) password string * @return command processing error code */ -extern int NK_write_password_safe_slot(uint8_t slot_number, const char *slot_name, const char *slot_login, const char *slot_password); +extern int NK_write_password_safe_slot(uint8_t slot_number, const char *slot_name, + const char *slot_login, const char *slot_password); /** * Erase the password safe slot from the device @@ -292,6 +305,12 @@ extern int NK_write_password_safe_slot(uint8_t slot_number, const char *slot_nam * @return command processing error code */ extern int NK_erase_password_safe_slot(uint8_t slot_number); + +/** + * Check whether AES is supported by the device + * @return 0 for no and 1 for yes + */ +extern int NK_is_AES_supported(const char *user_password); } diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index fc1daa5..ed9c7b4 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -6,6 +6,7 @@ namespace nitrokey{ template <typename T> void strcpyT(T& dest, const char* src){ + assert(src != nullptr); const int s = sizeof dest; assert(strlen(src) <= s); strncpy((char*) &dest, src, s); @@ -138,8 +139,9 @@ namespace nitrokey{ } - bool NitrokeyManager::write_HOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint64_t hotp_counter, - bool use_8_digits, const char *temporary_password) { + bool NitrokeyManager::write_HOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint8_t hotp_counter, + bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, + const char *temporary_password) { assert(is_valid_hotp_slot_number(slot_number)); assert(strlen(secret)==20); //160 bits assert(strlen(slot_name)<=15); @@ -149,8 +151,11 @@ namespace nitrokey{ payload.slot_number = slot_number; strcpyT(payload.slot_secret, secret); strcpyT(payload.slot_name, slot_name); + strcpyT(payload.slot_token_id, token_ID); payload.slot_counter = hotp_counter; payload.use_8_digits = use_8_digits; + payload.use_enter = use_enter; + payload.use_tokenID = use_tokenID; auto auth = get_payload<Authorize>(); strcpyT(auth.temporary_password, temporary_password); @@ -161,8 +166,9 @@ namespace nitrokey{ return true; } - bool NitrokeyManager::write_TOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, - uint16_t time_window, bool use_8_digits, const char *temporary_password) { + 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<WriteToTOTPSlot>(); assert(is_valid_totp_slot_number(slot_number)); assert(strlen(secret) == sizeof payload.slot_secret); //160 bits @@ -172,18 +178,18 @@ namespace nitrokey{ payload.slot_number = slot_number; strcpyT(payload.slot_secret, secret); 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; auto auth = get_payload<Authorize>(); strcpyT(auth.temporary_password, temporary_password); auth.crc_to_authorize = WriteToTOTPSlot::CommandTransaction::getCRC(payload); Authorize::CommandTransaction::run(*device, auth); -// auto auth_successful = device->last_command_sucessfull(); auto resp = WriteToTOTPSlot::CommandTransaction::run(*device, payload); -// auto write_successful = device->last_command_sucessfull(); -// return auth_successful && write_successful; //left to show alternative approach return true; } @@ -270,6 +276,11 @@ namespace nitrokey{ } void NitrokeyManager::enable_password_safe(const char *user_pin) { + //The following command will cancel enabling PWS if it is not supported + auto a = get_payload<IsAESSupported>(); + strcpyT(a.user_password, user_pin); + IsAESSupported::CommandTransaction::run(*device, a); + auto p = get_payload<EnablePasswordSafe>(); strcpyT(p.user_password, user_pin); EnablePasswordSafe::CommandTransaction::run(*device, p); @@ -363,14 +374,16 @@ namespace nitrokey{ FactoryReset::CommandTransaction::run(*device, p); } - void NitrokeyManager::unlock_user_password(const char *admin_password) { + void NitrokeyManager::unlock_user_password(const char *admin_password, const char *new_user_password) { auto p = get_payload<UnlockUserPassword>(); strcpyT(p.admin_password, admin_password); + strcpyT(p.user_new_password, new_user_password); UnlockUserPassword::CommandTransaction::run(*device, p); } - void NitrokeyManager::write_config(bool numlock, bool capslock, bool scrolllock, bool enable_user_password, bool delete_user_password, const char *admin_temporary_password) { + 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<WriteGeneralConfig>(); p.numlock = (uint8_t) numlock; p.capslock = (uint8_t) capslock; @@ -390,4 +403,11 @@ namespace nitrokey{ return v; } + bool NitrokeyManager::is_AES_supported(const char *user_password) { + auto a = get_payload<IsAESSupported>(); + strcpyT(a.user_password, user_password); + IsAESSupported::CommandTransaction::run(*device, a); + return true; + } + }
\ No newline at end of file @@ -14,7 +14,7 @@ Device::Device() : m_vid(0), m_pid(0), m_retry_count(40), - m_retry_timeout(50), + m_retry_timeout(100), mp_devhandle(NULL), last_command_status(0){} @@ -85,7 +85,8 @@ Stick10::Stick10() { m_vid = 0x20a0; m_pid = 0x4108; m_model = DeviceModel::PRO; - m_send_receive_delay = 10ms; + m_send_receive_delay = 100ms; + m_retry_count = 100; } Stick20::Stick20() { diff --git a/include/NitrokeyManager.h b/include/NitrokeyManager.h index 4f1dcfa..7bc2673 100644 --- a/include/NitrokeyManager.h +++ b/include/NitrokeyManager.h @@ -21,10 +21,12 @@ namespace nitrokey { static NitrokeyManager *instance(); bool first_authenticate(const char *pin, const char *temporary_password); - bool write_HOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint64_t hotp_counter, - bool use_8_digits, const char *temporary_password); - bool write_TOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, - uint16_t time_window, bool use_8_digits, const char *temporary_password); + bool write_HOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint8_t hotp_counter, + bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, + const char *temporary_password); + bool write_TOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window, + bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, + const char *temporary_password); uint32_t get_HOTP_code(uint8_t slot_number, const char *user_temporary_password); uint32_t get_TOTP_code(uint8_t slot_number, uint64_t challenge, uint64_t last_totp_time, uint8_t last_interval, const char *user_temporary_password); @@ -68,13 +70,15 @@ namespace nitrokey { void build_aes_key(const char *admin_password); - void unlock_user_password(const char *admin_password); + void unlock_user_password(const char *admin_password, const char *new_user_password); - void write_config(bool numlock, bool capslock, bool scrolllock, bool enable_user_password, + void write_config(uint8_t numlock, uint8_t capslock, uint8_t scrolllock, bool enable_user_password, bool delete_user_password, const char *admin_temporary_password); vector<uint8_t> read_config(); + bool is_AES_supported(const char *user_password); + private: NitrokeyManager(); ~NitrokeyManager(); diff --git a/include/device.h b/include/device.h index ffc38e5..0553d2e 100644 --- a/include/device.h +++ b/include/device.h @@ -50,7 +50,7 @@ public: std::chrono::milliseconds get_retry_timeout() const { return m_retry_timeout; }; std::chrono::milliseconds get_send_receive_delay() const {return m_send_receive_delay;} - int get_last_command_status() const; + int get_last_command_status() {auto a = last_command_status; last_command_status = 0; return a;}; void set_last_command_status(uint8_t _err) { last_command_status = _err;} ; bool last_command_sucessfull() const {return last_command_status == 0;}; DeviceModel get_device_model() const {return m_model;} diff --git a/include/device_proto.h b/include/device_proto.h index 6e21f9f..78abe38 100644 --- a/include/device_proto.h +++ b/include/device_proto.h @@ -215,11 +215,14 @@ class Transaction : semantics::non_constructible { Log::instance()("Incoming HID packet:", Loglevel::DEBUG); Log::instance()((std::string)(resp), Loglevel::DEBUG); + Log::instance()(std::string("Retry count: ") + std::to_string(retry), Loglevel::DEBUG); if (!resp.isValid()) throw std::runtime_error("Invalid incoming packet"); + if (retry <= 0) throw std::runtime_error("Maximum retry count reached for receiving response from the device!"); if (resp.last_command_status!=0) throw CommandFailedException(resp.command_id, resp.last_command_status); - // See: DeviceResponse + + // See: DeviceResponse return resp.payload; } diff --git a/include/stick10_commands.h b/include/stick10_commands.h index 6df8727..ef83747 100644 --- a/include/stick10_commands.h +++ b/include/stick10_commands.h @@ -81,7 +81,6 @@ class SetTime : Command<CommandID::SET_TIME> { }; -// TODO duplicate TOTP class WriteToHOTPSlot : Command<CommandID::WRITE_TO_SLOT> { public: struct CommandPayload { @@ -96,7 +95,15 @@ class WriteToHOTPSlot : Command<CommandID::WRITE_TO_SLOT> { bool use_tokenID : 1; }; }; - uint8_t slot_token_id[13]; + 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; + }; uint64_t slot_counter; bool isValid() const { return !(slot_number & 0xF0); } @@ -137,7 +144,15 @@ class WriteToTOTPSlot : Command<CommandID::WRITE_TO_SLOT> { bool use_tokenID : 1; }; }; - uint8_t slot_token_id[13]; + union{ + uint8_t slot_token_id[13]; /** OATH Token Identifier */ + struct{ /** @see https://openauthentication.org/token-specs/ */ + uint8_t omp[2]; + uint8_t tt[2]; + uint8_t mui[8]; + uint8_t keyboard_layout; //disabled feature in nitroapp as of 20160805 + } slot_token_fields; + }; uint16_t slot_interval; bool isValid() const { return !(slot_number & 0xF0); } //TODO check @@ -160,27 +175,6 @@ class WriteToTOTPSlot : Command<CommandID::WRITE_TO_SLOT> { CommandTransaction; }; -class GetCode : Command<CommandID::GET_CODE> { - public: - struct CommandPayload { - uint8_t slot_number; - uint64_t challenge; - uint64_t last_totp_time; - uint8_t last_interval; - - bool isValid() const { return !(slot_number & 0xF0); } - } __packed; - - struct ResponsePayload { - uint8_t code[18]; - - bool isValid() const { return true; } - } __packed; - - typedef Transaction<command_id(), struct CommandPayload, - struct ResponsePayload> CommandTransaction; -}; - class GetTOTP : Command<CommandID::GET_CODE> { public: struct CommandPayload { @@ -202,18 +196,28 @@ class GetTOTP : Command<CommandID::GET_CODE> { struct ResponsePayload { union { - uint8_t whole_response[18]; //TODO remove if not needed + uint8_t whole_response[18]; //14 bytes reserved for config, but used only 1 struct { uint32_t code; - uint8_t config; - } __packed; - } __packed; + 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 << "config:\t" << "TODO" /*(config) */<< std::endl; //TODO show byte field options + 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; @@ -237,10 +241,17 @@ class GetHOTP : Command<CommandID::GET_CODE> { struct ResponsePayload { union { - uint8_t whole_response[18]; //TODO remove if not needed + uint8_t whole_response[18]; //14 bytes reserved for config, but used only 1 struct { uint32_t code; - uint8_t config; + union{ + uint8_t _slot_config; + struct{ + bool use_8_digits : 1; + bool use_enter : 1; + bool use_tokenID : 1; + }; + }; } __packed; } __packed; @@ -248,7 +259,10 @@ class GetHOTP : Command<CommandID::GET_CODE> { std::string dissect() const { std::stringstream ss; ss << "code:\t" << (code) << std::endl; - ss << "config:\t" << "TODO" /*(config) */<< std::endl; //TODO show byte field options + 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; @@ -301,14 +315,14 @@ class GetStatus : Command<CommandID::GET_STATUS> { union { uint8_t general_config[5]; struct{ - uint8_t numlock; - uint8_t capslock; - uint8_t scrolllock; + 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; }; }; - bool isValid() const { return true; } + bool isValid() const { return enable_user_password!=delete_user_password; } std::string dissect() const { std::stringstream ss; @@ -319,9 +333,9 @@ class GetStatus : Command<CommandID::GET_STATUS> { ss << "general_config:\t" << ::nitrokey::misc::hexdump((const char *)(general_config), sizeof general_config); - ss << "numlock:\t" << (bool)numlock << std::endl; - ss << "capslock:\t" << (bool)capslock << std::endl; - ss << "scrolllock:\t" << (bool)scrolllock << std::endl; + 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; @@ -557,13 +571,18 @@ class EnablePasswordSafe : Command<CommandID::PW_SAFE_ENABLE> { }; class PasswordSafeInitKey : Command<CommandID::PW_SAFE_INIT_KEY> { + /** + * never used in Nitrokey App + */ public: typedef Transaction<command_id(), struct EmptyPayload, struct EmptyPayload> CommandTransaction; }; -// TODO naming screwed up, see above class PasswordSafeSendSlotViaHID : Command<CommandID::PW_SAFE_SEND_DATA> { + /** + * never used in Nitrokey App + */ public: struct CommandPayload { uint8_t slot_number; @@ -584,18 +603,18 @@ class WriteGeneralConfig : Command<CommandID::WRITE_CONFIG> { union{ uint8_t config[5]; struct{ - uint8_t numlock; - uint8_t capslock; - uint8_t scrolllock; + uint8_t numlock; /** 0-1: HOTP slot number from which the code will be get on double press, other value - function disabled */ + uint8_t capslock; /** same as numlock */ + uint8_t scrolllock; /** same as numlock */ uint8_t enable_user_password; uint8_t delete_user_password; }; }; std::string dissect() const { std::stringstream ss; - ss << "numlock:\t" << (bool)numlock << std::endl; - ss << "capslock:\t" << (bool)capslock << std::endl; - ss << "scrolllock:\t" << (bool)scrolllock << std::endl; + 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(); @@ -666,7 +685,7 @@ class Authorize : Command<CommandID::AUTHORIZE> { class UserAuthorize : Command<CommandID::USER_AUTHORIZE> { public: struct CommandPayload { - uint64_t crc_to_authorize; + uint32_t crc_to_authorize; uint8_t temporary_password[25]; std::string dissect() const { std::stringstream ss; @@ -683,7 +702,8 @@ class UserAuthorize : Command<CommandID::USER_AUTHORIZE> { class UnlockUserPassword : Command<CommandID::UNLOCK_USER_PASSWORD> { public: struct CommandPayload { - uint8_t admin_password[20]; + uint8_t admin_password[25]; + uint8_t user_new_password[25]; std::string dissect() const { std::stringstream ss; ss << " admin_password:\t" << admin_password<< std::endl; @@ -691,8 +711,6 @@ class UnlockUserPassword : Command<CommandID::UNLOCK_USER_PASSWORD> { } } __packed; - // TODO could we get the stick to return the retry count? - typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> CommandTransaction; }; @@ -714,11 +732,15 @@ class ChangeUserPin : Command<CommandID::CHANGE_USER_PIN> { CommandTransaction; }; -// TODO why is it needed? class IsAESSupported : Command<CommandID::DETECT_SC_AES> { public: struct CommandPayload { - uint8_t password[20]; + uint8_t user_password[20]; + std::string dissect() const { + std::stringstream ss; + ss << " user_password:\t" << user_password<< std::endl; + return ss.str(); + } } __packed; typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> diff --git a/unittest/test_bindings.py b/unittest/test_bindings.py index afe7d0d..5688adb 100644 --- a/unittest/test_bindings.py +++ b/unittest/test_bindings.py @@ -20,6 +20,7 @@ class DeviceErrorCode(Enum): NOT_PROGRAMMED = 3 WRONG_PASSWORD = 4 STATUS_NOT_AUTHORIZED = 5 + STATUS_AES_DEC_FAILED = 0xa @pytest.fixture(scope="module") @@ -30,25 +31,28 @@ def C(request): with open(fp, 'r') as f: declarations = f.readlines() - for declaration in declarations: - # extern int NK_write_totp_slot(int slot_number, char* secret, int time_window); - if 'extern' in declaration and not '"C"' in declaration: + a = iter(declarations) + for declaration in a: + if declaration.startswith('extern') and not '"C"' in declaration: declaration = declaration.replace('extern', '').strip() + while not ';' in declaration: + declaration += (next(a)).strip() print(declaration) ffi.cdef(declaration) C = ffi.dlopen("../build/libnitrokey.so") C.NK_set_debug(False) C.NK_login('P') + # assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK # assert C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP) == DeviceErrorCode.STATUS_OK # C.NK_status() def fin(): - print ('\nFinishing connection to device') + print('\nFinishing connection to device') C.NK_logout() - print ('Finished') + print('Finished') request.addfinalizer(fin) C.NK_set_debug(True) @@ -107,11 +111,64 @@ def test_password_safe_slot_status(C): safe_slot_status = C.NK_get_password_safe_slot_status() assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK is_slot_programmed = list(ffi.cast("uint8_t [16]", safe_slot_status)[0:16]) - print ((is_slot_programmed, len(is_slot_programmed))) + print((is_slot_programmed, len(is_slot_programmed))) assert is_slot_programmed[0] == 0 assert is_slot_programmed[1] == 1 +@pytest.mark.xfail(run=False, reason="issue to register: device locks up after below commands sequence (reinsertion fixes), skipping for now") +def test_issue_device_locks_on_second_key_generation_in_sequence(C): + assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK + assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK + + +def test_regenerate_aes_key(C): + C.NK_set_debug(True) + assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK + assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK + + +@pytest.mark.xfail(reason="firmware bug: regenerating AES key command not always results in cleared slot data") +def test_destroy_password_safe(C): + """ + Sometimes fails on NK Pro - slot name is not cleared ergo key generation has not succeed despite the success result + returned from the device + """ + C.NK_set_debug(True) + assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK + # write password safe slot + assert C.NK_write_password_safe_slot(0, 'slotname1', 'login1', 'pass1') == DeviceErrorCode.STATUS_OK + # read slot + assert gs(C.NK_get_password_safe_slot_name(0)) == 'slotname1' + assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK + slot_login = C.NK_get_password_safe_slot_login(0) + assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK + assert gs(slot_login) == 'login1' + # destroy password safe by regenerating aes key + assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK + + assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK + assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK + + assert gs(C.NK_get_password_safe_slot_name(0)) != 'slotname1' + assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK + + # check was slot status cleared + safe_slot_status = C.NK_get_password_safe_slot_status() + assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK + is_slot_programmed = list(ffi.cast("uint8_t [16]", safe_slot_status)[0:16]) + assert is_slot_programmed[0] == 0 + + +def test_is_AES_supported(C): + assert C.NK_is_AES_supported('wrong password') != 1 + assert C.NK_get_last_command_status() == DeviceErrorCode.WRONG_PASSWORD + assert C.NK_is_AES_supported(DefaultPasswords.USER) == 1 + assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK + + def test_admin_PIN_change(C): new_password = '123123123' assert C.NK_change_admin_PIN('wrong_password', new_password) == DeviceErrorCode.WRONG_PASSWORD @@ -143,60 +200,106 @@ def test_user_retry_counts(C): assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK assert C.NK_get_user_retry_count() == default_user_retry_count + +def test_unlock_user_password(C): + C.NK_set_debug(True) + default_user_retry_count = 3 + default_admin_retry_count = 3 + new_password = '123123123' + assert C.NK_get_user_retry_count() == default_user_retry_count + assert C.NK_change_user_PIN('wrong_password', new_password) == DeviceErrorCode.WRONG_PASSWORD + assert C.NK_change_user_PIN('wrong_password', new_password) == DeviceErrorCode.WRONG_PASSWORD + assert C.NK_change_user_PIN('wrong_password', new_password) == DeviceErrorCode.WRONG_PASSWORD + assert C.NK_get_user_retry_count() == default_user_retry_count - 3 + assert C.NK_get_admin_retry_count() == default_admin_retry_count + + assert C.NK_unlock_user_password('wrong password', DefaultPasswords.USER) == DeviceErrorCode.WRONG_PASSWORD + assert C.NK_get_admin_retry_count() == default_admin_retry_count - 1 + assert C.NK_unlock_user_password(DefaultPasswords.ADMIN, DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK + assert C.NK_get_user_retry_count() == default_user_retry_count + assert C.NK_get_admin_retry_count() == default_admin_retry_count + + def test_admin_auth(C): assert C.NK_first_authenticate('wrong_password', DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.WRONG_PASSWORD assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + def test_user_auth(C): assert C.NK_user_authenticate('wrong_password', DefaultPasswords.USER_TEMP) == DeviceErrorCode.WRONG_PASSWORD assert C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP) == DeviceErrorCode.STATUS_OK -def check_RFC_codes(C, func, prep=None): +def check_HOTP_RFC_codes(C, func, prep=None, use_8_digits=False): + """ + # https://tools.ietf.org/html/rfc4226#page-32 + """ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK - assert C.NK_write_hotp_slot(1, 'python_test', RFC_SECRET, 0, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert C.NK_write_hotp_slot(1, 'python_test', RFC_SECRET, 0, use_8_digits, False, False, "", + DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK test_data = [ - 755224, 287082, 359152, 969429, 338314, 254676, 287922, 162583, 399871, 520489, + 1284755224, 1094287082, 137359152, 1726969429, 1640338314, 868254676, 1918287922, 82162583, 673399871, + 645520489, ] for code in test_data: if prep: prep() r = func(1) - assert code == r + code = str(code)[-8:] if use_8_digits else str(code)[-6:] + assert int(code) == r -@pytest.mark.skip(reason="not working correctly, skipping for now") -def test_HOTP_RFC_pin_protection(C): - C.NK_set_debug(True) - assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK - assert C.NK_write_config(True, True, True, True, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +@pytest.mark.parametrize("use_8_digits", [False, True, ]) +@pytest.mark.parametrize("use_pin_protection", [False, True, ]) +def test_HOTP_RFC_use8digits_usepin(C, use_8_digits, use_pin_protection): assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK - assert C.NK_write_hotp_slot(1, 'python_test', RFC_SECRET, 0, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK - # check_RFC_codes(C, lambda x: C.NK_get_hotp_code_PIN(x, DefaultPasswords.USER_TEMP), lambda: C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP)) - assert C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP) == DeviceErrorCode.STATUS_OK - assert C.NK_get_hotp_code_PIN(1, DefaultPasswords.USER_TEMP) == 755224 - assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK - - -@pytest.mark.skip(reason="not implemented yet") -def test_HOTP_RFC_no_pin_protection_8digits(C): - assert False # TODO to write - -def test_HOTP_RFC_no_pin_protection(C): + assert C.NK_write_config(255, 255, 255, use_pin_protection, not use_pin_protection, + DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + if use_pin_protection: + check_HOTP_RFC_codes(C, + lambda x: C.NK_get_hotp_code_PIN(x, DefaultPasswords.USER_TEMP), + lambda: C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP), + use_8_digits=use_8_digits) + else: + check_HOTP_RFC_codes(C, C.NK_get_hotp_code, use_8_digits=use_8_digits) + + +def test_HOTP_token(C): + """ + Check HOTP routine with written token ID to slot. + """ + use_pin_protection = False assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK - assert C.NK_write_hotp_slot(1, 'python_test', RFC_SECRET, 0, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert C.NK_write_config(255, 255, 255, use_pin_protection, not use_pin_protection, + DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK - assert C.NK_write_config(True, True, True, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK - # https://tools.ietf.org/html/rfc4226#page-32 - check_RFC_codes(C, C.NK_get_hotp_code) + token_ID = "AAV100000022" + assert C.NK_write_hotp_slot(1, 'python_test', RFC_SECRET, 0, False, False, True, token_ID, + DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + for i in range(5): + hotp_code = C.NK_get_hotp_code(1) + assert hotp_code != 0 + assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK -def test_TOTP_RFC_no_pin_protection(C): +@pytest.mark.xfail(reason="firmware bug: 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") +@pytest.mark.parametrize("PIN_protection", [False, True, ]) +def test_TOTP_RFC_usepin(C, PIN_protection): assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK - assert C.NK_write_config(True, True, True, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection, + DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK # test according to https://tools.ietf.org/html/rfc6238#appendix-B assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK - assert C.NK_write_totp_slot(1, 'python_test', RFC_SECRET, 30, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert C.NK_write_totp_slot(1, 'python_test', RFC_SECRET, 30, True, False, False, "", + DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + + get_func = None + if PIN_protection: + get_func = lambda x, y, z, r: C.NK_get_totp_code_PIN(x, y, z, r, DefaultPasswords.USER_TEMP) + else: + get_func = C.NK_get_totp_code + test_data = [ (59, 1, 94287082), (1111111109, 0x00000000023523EC, 7081804), @@ -204,8 +307,17 @@ def test_TOTP_RFC_no_pin_protection(C): (1234567890, 0x000000000273EF07, 89005924), ] for t, T, code in test_data: - C.NK_totp_set_time(t) - r = C.NK_get_totp_code(1, T, 0, 30) # FIXME T is not changing the outcome + """ + FIXME without the delay 50% of tests fails, with it only 12%, higher delay removes fails + -> set_time function not always works, to investigate why + """ + # import time + # time.sleep(2) + assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + if PIN_protection: + C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP) + assert C.NK_totp_set_time(t) == DeviceErrorCode.STATUS_OK # FIXME needs admin authentication + r = get_func(1, T, 0, 30) # FIXME T is not changing the outcome assert code == r @@ -228,7 +340,8 @@ def test_get_slot_names(C): def test_get_OTP_codes(C): - 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 assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK assert C.NK_erase_hotp_slot(0, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK @@ -247,18 +360,18 @@ def test_get_OTP_codes(C): def test_get_code_user_authorize(C): C.NK_set_debug(True) assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK - assert C.NK_write_totp_slot(0, 'python_otp_auth', RFC_SECRET, 30, True, + assert C.NK_write_totp_slot(0, 'python_otp_auth', RFC_SECRET, 30, True, False, False, "", DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK # enable PIN protection of OTP codes with write_config # TODO create convinience function on C API side to enable/disable OTP USER_PIN protection assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK - assert C.NK_write_config(True, True, True, True, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert C.NK_write_config(255, 255, 255, True, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK code = C.NK_get_totp_code(0, 0, 0, 0) assert code == 0 assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_NOT_AUTHORIZED # disable PIN protection with write_config assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK - assert C.NK_write_config(True, True, True, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert C.NK_write_config(255, 255, 255, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK code = C.NK_get_totp_code(0, 0, 0, 0) assert code != 0 assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK @@ -273,18 +386,45 @@ def cast_pointer_to_tuple(obj, typen, len): def test_read_write_config(C): C.NK_set_debug(True) - # let's set sample config with pin protection and disabled capslock + # let's set sample config with pin protection and disabled scrolllock assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK - assert C.NK_write_config(True, False, True, True, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert C.NK_write_config(0, 1, 2, True, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK config_raw_data = C.NK_read_config() assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK config = cast_pointer_to_tuple(config_raw_data, 'uint8_t', 5) - assert config == (True, False, True, True, False) + assert config == (0, 1, 2, True, False) # restore defaults and check assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK - assert C.NK_write_config(True, True, True, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert C.NK_write_config(255, 255, 255, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK config_raw_data = C.NK_read_config() assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK config = cast_pointer_to_tuple(config_raw_data, 'uint8_t', 5) - assert config == (True, True, True, False, True) + assert config == (255, 255, 255, False, True) + + +def wait(t): + import time + msg = 'Waiting for %d seconds' % t + print(msg.center(40, '=')) + time.sleep(t) + + +# @pytest.mark.skip() +def test_factory_reset(C): + 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 + assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert C.NK_write_hotp_slot(1, 'python_test', RFC_SECRET, 0, False, False, False, "", + DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert C.NK_get_hotp_code(1) == 755224 + assert C.NK_factory_reset(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK + wait(10) + assert C.NK_get_hotp_code(1) != 287082 + assert C.NK_get_last_command_status() == DeviceErrorCode.NOT_PROGRAMMED + # restore AES key + assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK + assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK + assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK |