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 | 
