diff options
| -rw-r--r-- | .idea/dictionaries/sz.xml | 1 | ||||
| -rw-r--r-- | CMakeLists.txt | 10 | ||||
| -rw-r--r-- | NK_C_API.cc | 2 | ||||
| -rw-r--r-- | NK_C_API.h | 2 | ||||
| -rw-r--r-- | NitrokeyManager.cc | 93 | ||||
| -rw-r--r-- | device.cc | 18 | ||||
| -rw-r--r-- | include/CommandFailedException.h | 5 | ||||
| -rw-r--r-- | include/LibraryException.h | 6 | ||||
| -rw-r--r-- | include/NitrokeyManager.h | 6 | ||||
| -rw-r--r-- | include/command_id.h | 42 | ||||
| -rw-r--r-- | include/device.h | 6 | ||||
| -rw-r--r-- | include/device_proto.h | 480 | ||||
| -rw-r--r-- | include/dissect.h | 10 | ||||
| -rw-r--r-- | include/stick10_commands.h | 14 | ||||
| -rw-r--r-- | include/stick20_commands.h | 50 | ||||
| -rw-r--r-- | unittest/catch_main.cpp | 2 | ||||
| -rw-r--r-- | unittest/test_C_API.cpp | 34 | ||||
| -rw-r--r-- | unittest/test_bindings.py | 176 | 
18 files changed, 668 insertions, 289 deletions
| diff --git a/.idea/dictionaries/sz.xml b/.idea/dictionaries/sz.xml index 59b56fb..c8c18a3 100644 --- a/.idea/dictionaries/sz.xml +++ b/.idea/dictionaries/sz.xml @@ -1,6 +1,7 @@  <component name="ProjectDictionaryState">    <dictionary name="sz">      <words> +      <w>loglevel</w>        <w>nitrokey</w>        <w>totp</w>      </words> diff --git a/CMakeLists.txt b/CMakeLists.txt index 39c0c34..eedfd35 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,13 @@  cmake_minimum_required(VERSION 3.5)  project(libnitrokey) +set(CMAKE_CXX_COMPILER "/usr/bin/clang++-3.8" CACHE string "clang++ compiler" FORCE) +  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") +SET(CMAKE_CXX_FLAGS  "${CMAKE_CXX_FLAGS} -fPIC -Wno-gnu-variable-sized-type-not-at-end -g3" ) +SET(CMAKE_EXE_LINKER_FLAGS  "${CMAKE_EXE_LINKER_FLAGS} -lhidapi-libusb" ) + +include_directories(include unittest/Catch/include)  set(SOURCE_FILES      include/command.h @@ -22,6 +28,8 @@ set(SOURCE_FILES      log.cc      misc.cc      NitrokeyManager.cc -        NK_C_API.cc include/CommandFailedException.h include/LibraryException.h) +        NK_C_API.cc include/CommandFailedException.h include/LibraryException.h +        unittest/test_C_API.cpp +        unittest/catch_main.cpp)  add_executable(libnitrokey ${SOURCE_FILES})
\ No newline at end of file diff --git a/NK_C_API.cc b/NK_C_API.cc index 6265215..7110fca 100644 --- a/NK_C_API.cc +++ b/NK_C_API.cc @@ -223,7 +223,7 @@ 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, +extern int NK_write_hotp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint64_t hotp_counter,                                bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,                                const char *temporary_password) {      auto m = NitrokeyManager::instance(); @@ -167,7 +167,7 @@ extern int NK_erase_totp_slot(uint8_t slot_number, const char *temporary_passwor   * @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, +extern int NK_write_hotp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint64_t hotp_counter,                                bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,                                const char *temporary_password); diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index d827292..7f3bfd0 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -8,10 +8,15 @@ namespace nitrokey{      template <typename T>      void strcpyT(T& dest, const char* src){ -        if (src == nullptr) + +      if (src == nullptr)  //            throw EmptySourceStringException(slot_number);              return;          const size_t s_dest = sizeof dest; +      nitrokey::log::Log::instance()(std::string("strcpyT sizes dest src ") +                                     +std::to_string(s_dest)+ " " +                                     +std::to_string(strlen(src))+ " " +          ,nitrokey::log::Loglevel::DEBUG);          if (strlen(src) > s_dest){              throw TooLongStringException(strlen(src), s_dest, src);          } @@ -45,7 +50,7 @@ namespace nitrokey{      }      bool NitrokeyManager::connect() { -        device = nullptr; +        this->disconnect();          vector< shared_ptr<Device> > devices = { make_shared<Stick10>(), make_shared<Stick20>() };          for( auto & d : devices ){              if (d->connect()){ @@ -57,7 +62,8 @@ namespace nitrokey{      bool NitrokeyManager::connect(const char *device_model) { -        switch (device_model[0]){ +      this->disconnect(); +      switch (device_model[0]){              case 'P':                  device = make_shared<Stick10>();                  break; @@ -78,7 +84,12 @@ namespace nitrokey{      }      bool NitrokeyManager::disconnect() { -        return device->disconnect(); +      if (device == nullptr){ +        return false; +      } +      const auto res = device->disconnect(); +      device = nullptr; +      return res;      }      void NitrokeyManager::set_debug(bool state) { @@ -168,9 +179,9 @@ namespace nitrokey{          std::copy(vec.begin(), vec.end(), dest);      } -    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) { +    bool NitrokeyManager::write_HOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint64_t hotp_counter, +                                          bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, +                                          const char *temporary_password) {          if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number);          slot_number = get_internal_slot_number_for_hotp(slot_number); @@ -180,7 +191,22 @@ namespace nitrokey{          vector_copy(payload.slot_secret, secret_bin);          strcpyT(payload.slot_name, slot_name);          strcpyT(payload.slot_token_id, token_ID); -        payload.slot_counter = hotp_counter; +      switch (device->get_device_model() ){ +        case DeviceModel::PRO: { +          payload.slot_counter = hotp_counter; +          break; +        } +        case DeviceModel::STORAGE: { +          std::string counter = std::to_string(hotp_counter); +          strcpyT(payload.slot_counter, counter.c_str()); +          break; +        } +        default: +          nitrokey::log::Log::instance()(  std::string(__FILE__) + std::to_string(__LINE__) + +                   std::string(__FUNCTION__) + std::string(" Unhandled device model for HOTP") +              , nitrokey::log::Loglevel::DEBUG); +          break; +      }          payload.use_8_digits = use_8_digits;          payload.use_enter = use_enter;          payload.use_tokenID = use_tokenID; @@ -280,11 +306,10 @@ namespace nitrokey{                  auto p = get_payload<ChangeAdminUserPin20Current>();                  strcpyT(p.old_pin, current_PIN);                  p.set_kind(StoKind); -                ChangeAdminUserPin20Current::CommandTransaction::run(*device, p); -                  auto p2 = get_payload<ChangeAdminUserPin20New>();                  strcpyT(p2.new_pin, new_PIN);                  p2.set_kind(StoKind); +                ChangeAdminUserPin20Current::CommandTransaction::run(*device, p);                  ChangeAdminUserPin20New::CommandTransaction::run(*device, p2);              }                  break; @@ -312,10 +337,16 @@ namespace nitrokey{      }      uint8_t NitrokeyManager::get_user_retry_count() { +        if(device->get_device_model() == DeviceModel::STORAGE){ +          stick20::GetDeviceStatus::CommandTransaction::run(*device); +        }          auto response = GetUserPasswordRetryCount::CommandTransaction::run(*device);          return response.data().password_retry_count;      }      uint8_t NitrokeyManager::get_admin_retry_count() { +        if(device->get_device_model() == DeviceModel::STORAGE){ +          stick20::GetDeviceStatus::CommandTransaction::run(*device); +        }          auto response = GetPasswordRetryCount::CommandTransaction::run(*device);          return response.data().password_retry_count;      } @@ -380,9 +411,21 @@ namespace nitrokey{      }      void NitrokeyManager::build_aes_key(const char *admin_password) { -        auto p = get_payload<BuildAESKey>(); -        strcpyT(p.admin_password, admin_password); -        BuildAESKey::CommandTransaction::run(*device, p); +        switch (device->get_device_model()) { +            case DeviceModel::PRO: { +                auto p = get_payload<BuildAESKey>(); +                strcpyT(p.admin_password, admin_password); +                BuildAESKey::CommandTransaction::run(*device, p); +                break; +            } +            case DeviceModel::STORAGE : { +                auto p = get_payload<stick20::CreateNewKeys>(); +                strcpyT(p.admin_password, admin_password); +                p.setKindPrefixed(); +                stick20::CreateNewKeys::CommandTransaction::run(*device, p); +                break; +            } +        }      }      void NitrokeyManager::factory_reset(const char *admin_password) { @@ -392,10 +435,26 @@ namespace nitrokey{      }      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); +      switch (device->get_device_model()){ +        case DeviceModel::PRO: { +          auto p = get_payload<stick10::UnlockUserPassword>(); +          strcpyT(p.admin_password, admin_password); +          strcpyT(p.user_new_password, new_user_password); +          stick10::UnlockUserPassword::CommandTransaction::run(*device, p); +          break; +        } +        case DeviceModel::STORAGE : { +          auto p2 = get_payload<ChangeAdminUserPin20Current>(); +          p2.set_kind(PasswordKind::Admin); +          strcpyT(p2.old_pin, admin_password); +          ChangeAdminUserPin20Current::CommandTransaction::run(*device, p2); +          auto p3 = get_payload<stick20::UnlockUserPassword>(); +          p3.set_kind(PasswordKind::Admin); +          strcpyT(p3.user_new_password, new_user_password); +          stick20::UnlockUserPassword::CommandTransaction::run(*device, p3); +          break; +        } +      }      } @@ -13,7 +13,8 @@ using namespace nitrokey::log;  Device::Device()      : m_vid(0),        m_pid(0), -      m_retry_count(40), +      m_retry_sending_count(3), +      m_retry_receiving_count(40),        m_retry_timeout(100),        mp_devhandle(NULL),        last_command_status(0){} @@ -21,14 +22,16 @@ Device::Device()  bool Device::disconnect() {    Log::instance()(__PRETTY_FUNCTION__, Loglevel::DEBUG_L2); -  hid_exit(); +  if(mp_devhandle== nullptr) return false; +  hid_close(mp_devhandle);    mp_devhandle = NULL; +  hid_exit();    return true;  }  bool Device::connect() {    Log::instance()(__PRETTY_FUNCTION__, Loglevel::DEBUG_L2); -  // hid_init(); +//   hid_init();    mp_devhandle = hid_open(m_vid, m_pid, NULL);    // hid_init();    return mp_devhandle != NULL; @@ -67,7 +70,7 @@ int Device::recv(void *packet) {                      Loglevel::DEBUG_L2);      if (status > 0) break;  // success -    if (retry_count++ >= m_retry_count) { +    if (retry_count++ >= m_retry_receiving_count) {        Log::instance()(            "Maximum retry count reached" + std::to_string(retry_count),            Loglevel::WARNING); @@ -86,13 +89,14 @@ Stick10::Stick10() {    m_pid = 0x4108;    m_model = DeviceModel::PRO;      m_send_receive_delay = 100ms; -  m_retry_count = 100; +  m_retry_receiving_count = 100;  }  Stick20::Stick20() {    m_vid = 0x20a0;    m_pid = 0x4109; -  m_retry_timeout = std::chrono::milliseconds(500); +  m_retry_timeout = 20ms;    m_model = DeviceModel::STORAGE; -    m_send_receive_delay = 1000ms; +  m_send_receive_delay = 20ms; +  m_retry_receiving_count = 40;  } diff --git a/include/CommandFailedException.h b/include/CommandFailedException.h index 3306f7b..9b0c59e 100644 --- a/include/CommandFailedException.h +++ b/include/CommandFailedException.h @@ -7,6 +7,7 @@  #include <exception>  #include <cstdint> +#include <log.h>  class CommandFailedException : public std::exception {  public: @@ -15,7 +16,9 @@ public:      CommandFailedException(uint8_t last_command_code, uint8_t last_command_status) :              last_command_code(last_command_code), -            last_command_status(last_command_status){} +            last_command_status(last_command_status){ +      nitrokey::log::Log::instance()(std::string("CommandFailedException, status: ")+ std::to_string(last_command_status), nitrokey::log::Loglevel::DEBUG); +    }      virtual const char *what() const throw() {          return "Command execution has failed on device"; diff --git a/include/LibraryException.h b/include/LibraryException.h index 72891fb..3c3fab4 100644 --- a/include/LibraryException.h +++ b/include/LibraryException.h @@ -4,6 +4,7 @@  #include <exception>  #include <cstdint>  #include <string> +#include "log.h"  class LibraryException: std::exception {  public: @@ -83,7 +84,10 @@ public:      std::string message;      TooLongStringException(size_t size_source, size_t size_destination, const std::string &message = "") : size_source( -            size_source), size_destination(size_destination), message(message) {} +            size_source), size_destination(size_destination), message(message) { +      nitrokey::log::Log::instance()(std::string("TooLongStringException, size diff: ")+ std::to_string(size_source-size_destination), nitrokey::log::Loglevel::DEBUG); + +    }      virtual const char *what() const throw() override {          //TODO add sizes and message data to final message diff --git a/include/NitrokeyManager.h b/include/NitrokeyManager.h index 1e518f4..52c18d7 100644 --- a/include/NitrokeyManager.h +++ b/include/NitrokeyManager.h @@ -22,9 +22,9 @@ namespace nitrokey {          static shared_ptr <NitrokeyManager> instance();          bool first_authenticate(const char *pin, const char *temporary_password); -        bool write_HOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint8_t hotp_counter, -                                     bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, -                                     const char *temporary_password); +        bool write_HOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint64_t hotp_counter, +                             bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, +                             const char *temporary_password);          bool write_TOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window,                                       bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,                                       const char *temporary_password); diff --git a/include/command_id.h b/include/command_id.h index 87d270e..8148cc1 100644 --- a/include/command_id.h +++ b/include/command_id.h @@ -4,8 +4,42 @@  namespace nitrokey {  namespace proto { - -#define STICK20_CMD_START_VALUE 0x20 +    namespace stick20 { +      enum class device_status : uint8_t { +        idle = 0, +        ok, +        busy, +        wrong_password, +        busy_progressbar, +        password_matrix_ready, +        no_user_password_unlock, +        smartcard_error, +        security_bit_active +      }; +      const int CMD_START_VALUE = 0x20; +      const int CMD_END_VALUE = 0x60; +    } +    namespace stick10 { +      enum class command_status : uint8_t { +          ok = 0, +          wrong_CRC, +          wrong_slot, +          slot_not_programmed, +          wrong_password  = 4, +          not_authorized, +          timestamp_warning, +          no_name_error, +          not_supported, +          unknown_command, +          AES_dec_failed +      }; +      enum class device_status : uint8_t { +        ok = 0, +        busy = 1, +        error, +        received_report, +      }; +    }  enum class CommandID : uint8_t { @@ -32,8 +66,8 @@ enum class CommandID : uint8_t {    CHANGE_USER_PIN = 0x14,    CHANGE_ADMIN_PIN = 0x15, -  STICK20_CMD_SEND_PASSWORD = STICK20_CMD_START_VALUE + 18, -  STICK20_CMD_SEND_NEW_PASSWORD = STICK20_CMD_START_VALUE + 19, +  STICK20_CMD_SEND_PASSWORD = stick20::CMD_START_VALUE + 18, +  STICK20_CMD_SEND_NEW_PASSWORD = stick20::CMD_START_VALUE + 19,    ENABLE_CRYPTED_PARI = 0x20,    DISABLE_CRYPTED_PARI, diff --git a/include/device.h b/include/device.h index 67b739c..34b7a5b 100644 --- a/include/device.h +++ b/include/device.h @@ -38,7 +38,8 @@ public:     */    virtual int recv(void *packet); -  int get_retry_count() const { return m_retry_count; }; +  int get_retry_receiving_count() const { return m_retry_receiving_count; }; +  int get_retry_sending_count() const { return m_retry_sending_count; };    std::chrono::milliseconds get_retry_timeout() const { return m_retry_timeout; };      std::chrono::milliseconds get_send_receive_delay() const {return m_send_receive_delay;} @@ -59,7 +60,8 @@ private:     *	library, there's no way of doing it asynchronously,     *	hence polling.     */ -  int m_retry_count; +  int m_retry_sending_count; +  int m_retry_receiving_count;    std::chrono::milliseconds m_retry_timeout;    std::chrono::milliseconds m_send_receive_delay; diff --git a/include/device_proto.h b/include/device_proto.h index 4044cdc..45f165b 100644 --- a/include/device_proto.h +++ b/include/device_proto.h @@ -1,5 +1,6 @@  #ifndef DEVICE_PROTO_H  #define DEVICE_PROTO_H +  #include <utility>  #include <thread>  #include <type_traits> @@ -31,7 +32,7 @@  #define PWS_SEND_CR 3  namespace nitrokey { -namespace proto { +    namespace proto {  /*   *	POD types for HID proto commands   *	Instances are meant to be __packed. @@ -42,45 +43,45 @@ namespace proto {  /*   *	Every packet is a USB HID report (check USB spec)   */ -template <CommandID cmd_id, typename Payload> -struct HIDReport { -  uint8_t _zero; -  CommandID command_id;  // uint8_t -  union { -    uint8_t _padding[HID_REPORT_SIZE - 6]; -    Payload payload; -  } __packed; -  uint32_t crc; - -  // POD types can't have non-default constructors -  // used in Transaction<>::run() -  void initialize() { -    bzero(this, sizeof *this); -    command_id = cmd_id; -  } - -  uint32_t calculate_CRC() const { -    // w/o leading zero, a part of each HID packet -    // w/o 4-byte crc -    return misc::stm_crc32((const uint8_t *)(this) + 1, -                           (size_t)(HID_REPORT_SIZE - 5)); -  } - -  void update_CRC() { crc = calculate_CRC(); } - -  bool isCRCcorrect() const { return crc == calculate_CRC(); } - -  bool isValid() const { -    return true; -    //		return !_zero && payload.isValid() && isCRCcorrect(); -  } - -  operator std::string() const { -    // Packet type is known upfront in normal operation. -    // Can't be used to dissect random packets. -    return QueryDissector<cmd_id, decltype(*this)>::dissect(*this); -  } -} __packed; +        template<CommandID cmd_id, typename Payload> +        struct HIDReport { +            uint8_t _zero; +            CommandID command_id;  // uint8_t +            union { +                uint8_t _padding[HID_REPORT_SIZE - 6]; +                Payload payload; +            } __packed; +            uint32_t crc; + +            // POD types can't have non-default constructors +            // used in Transaction<>::run() +            void initialize() { +              bzero(this, sizeof *this); +              command_id = cmd_id; +            } + +            uint32_t calculate_CRC() const { +              // w/o leading zero, a part of each HID packet +              // w/o 4-byte crc +              return misc::stm_crc32((const uint8_t *) (this) + 1, +                                     (size_t) (HID_REPORT_SIZE - 5)); +            } + +            void update_CRC() { crc = calculate_CRC(); } + +            bool isCRCcorrect() const { return crc == calculate_CRC(); } + +            bool isValid() const { +              return true; +              //		return !_zero && payload.isValid() && isCRCcorrect(); +            } + +            operator std::string() const { +              // Packet type is known upfront in normal operation. +              // Can't be used to dissect random packets. +              return QueryDissector<cmd_id, decltype(*this)>::dissect(*this); +            } +        } __packed;  /*   *	Response payload (the parametrized type inside struct HIDReport) @@ -88,175 +89,238 @@ struct HIDReport {   *	command_id member in incoming HIDReport structure carries the command   *	type last used.   */ -template <CommandID cmd_id, typename ResponsePayload> -struct DeviceResponse { -  uint8_t _zero; -  uint8_t device_status; -  uint8_t command_id;  // originally last_command_type -  uint32_t last_command_crc; -  uint8_t last_command_status; -  union { -    uint8_t _padding[HID_REPORT_SIZE - 12]; -    ResponsePayload payload; -  } __packed; -  uint32_t crc; - -  void initialize() { bzero(this, sizeof *this); } - -  uint32_t calculate_CRC() const { -    // w/o leading zero, a part of each HID packet -    // w/o 4-byte crc -    return misc::stm_crc32((const uint8_t *)(this) + 1, -                           (size_t)(HID_REPORT_SIZE - 5)); -  } - -  void update_CRC() { crc = calculate_CRC(); } - -  bool isCRCcorrect() const { return crc == calculate_CRC(); } - -  bool isValid() const { -    //		return !_zero && payload.isValid() && isCRCcorrect() && -    //				command_id == (uint8_t)(cmd_id); -    return true; -  } - -  operator std::string() const { -    return ResponseDissector<cmd_id, decltype(*this)>::dissect(*this); -  } -} __packed; - -struct EmptyPayload { -  uint8_t _data[]; - -  bool isValid() const { return true; } - -  std::string dissect() const { return std::string("Empty Payload."); } -} __packed; - -template <typename command_packet, typename response_payload> -class ClearingProxy{ -public: -    ClearingProxy(command_packet &p){ -        packet = p; -        bzero(&p, sizeof(p)); -    } -    ~ClearingProxy(){ -        bzero(&packet, sizeof(packet)); -    } - -    response_payload & data() { -        return packet.payload; -    } - -    command_packet packet; -}; - -template <CommandID cmd_id, typename command_payload, typename response_payload> -class Transaction : semantics::non_constructible { - public: -  // Types declared in command class scope can't be reached from there. -  typedef command_payload CommandPayload; -  typedef response_payload ResponsePayload; - -  typedef struct HIDReport<cmd_id, CommandPayload> OutgoingPacket; -  typedef struct DeviceResponse<cmd_id, ResponsePayload> ResponsePacket; - -  static_assert(std::is_pod<OutgoingPacket>::value, -                "outgoingpacket must be a pod type"); -  static_assert(std::is_pod<ResponsePacket>::value, -                "ResponsePacket must be a POD type"); -  static_assert(sizeof(OutgoingPacket) == HID_REPORT_SIZE, -                "OutgoingPacket type is not the right size"); -  static_assert(sizeof(ResponsePacket) == HID_REPORT_SIZE, -                "ResponsePacket type is not the right size"); - -  static uint32_t getCRC( -          const command_payload &payload) { -    OutgoingPacket outp; -    outp.initialize(); -    outp.payload = payload; -    outp.update_CRC(); -    return outp.crc; -  } - -    template <typename T> -    static void clear_packet(T &st){ -        bzero(&st, sizeof(st)); -    } - - -    static ClearingProxy<ResponsePacket, response_payload> run(device::Device &dev, -                              const command_payload &payload) { -    using namespace ::nitrokey::device; -    using namespace ::nitrokey::log; -     using namespace std::chrono_literals; - -    Log::instance()(__PRETTY_FUNCTION__, Loglevel::DEBUG_L2); - -    int status; -    OutgoingPacket outp; -    ResponsePacket resp; - -    // POD types can't have non-default constructors -    outp.initialize(); -    resp.initialize(); - -    outp.payload = payload; -    outp.update_CRC(); - -    Log::instance()("Outgoing HID packet:", Loglevel::DEBUG); -    Log::instance()((std::string)(outp), Loglevel::DEBUG); - -    if (!outp.isValid()) throw std::runtime_error("Invalid outgoing packet"); - -    status = dev.send(&outp); -    if (status <= 0) -      throw std::runtime_error( -          std::string("Device error while sending command ") + -          std::to_string((int)(status))); - -      std::this_thread::sleep_for(dev.get_send_receive_delay()); - -      // FIXME make checks done in device:recv here -    int retry = dev.get_retry_count(); -    while (retry-- > 0) { -      status = dev.recv(&resp); - -      dev.set_last_command_status(resp.last_command_status); // FIXME should be handled on device.recv - -      if (resp.device_status == 0 && resp.last_command_crc == outp.crc) break; -      Log::instance()("Device is not ready or received packet's last CRC is not equal to sent CRC packet, retrying...", +        template<CommandID cmd_id, typename ResponsePayload> +        struct DeviceResponse { +            uint8_t _zero; +            uint8_t device_status; +            uint8_t command_id;  // originally last_command_type +            uint32_t last_command_crc; +            uint8_t last_command_status; +            union { +                uint8_t _padding[HID_REPORT_SIZE - 12]; +                ResponsePayload payload; +                struct { +                    uint8_t _storage_status_padding[20 - 8 + 1]; //starts on 20th byte minus already 8 used + zero byte +                    uint8_t command_counter; +                    uint8_t command_id; +                    uint8_t device_status; //@see stick20::device_status +                    uint8_t progress_bar_value; +                } storage_status __packed; +            } __packed; +            uint32_t crc; + +            void initialize() { bzero(this, sizeof *this); } + +            uint32_t calculate_CRC() const { +              // w/o leading zero, a part of each HID packet +              // w/o 4-byte crc +              return misc::stm_crc32((const uint8_t *) (this) + 1, +                                     (size_t) (HID_REPORT_SIZE - 5)); +            } + +            void update_CRC() { crc = calculate_CRC(); } + +            bool isCRCcorrect() const { return crc == calculate_CRC(); } + +            bool isValid() const { +              //		return !_zero && payload.isValid() && isCRCcorrect() && +              //				command_id == (uint8_t)(cmd_id); +              return crc != 0; +            } + +            operator std::string() const { +              return ResponseDissector<cmd_id, decltype(*this)>::dissect(*this); +            } +        } __packed; + +        struct EmptyPayload { +            uint8_t _data[]; + +            bool isValid() const { return true; } + +            std::string dissect() const { return std::string("Empty Payload."); } +        } __packed; + +        template<typename command_packet, typename response_payload> +        class ClearingProxy { +        public: +            ClearingProxy(command_packet &p) { +              packet = p; +              bzero(&p, sizeof(p)); +            } + +            ~ClearingProxy() { +              bzero(&packet, sizeof(packet)); +            } + +            response_payload &data() { +              return packet.payload; +            } + +            command_packet packet; +        }; + +        template<CommandID cmd_id, typename command_payload, typename response_payload> +        class Transaction : semantics::non_constructible { +        public: +            // Types declared in command class scope can't be reached from there. +            typedef command_payload CommandPayload; +            typedef response_payload ResponsePayload; + +            typedef struct HIDReport<cmd_id, CommandPayload> OutgoingPacket; +            typedef struct DeviceResponse<cmd_id, ResponsePayload> ResponsePacket; + +            static_assert(std::is_pod<OutgoingPacket>::value, +                          "outgoingpacket must be a pod type"); +            static_assert(std::is_pod<ResponsePacket>::value, +                          "ResponsePacket must be a POD type"); +            static_assert(sizeof(OutgoingPacket) == HID_REPORT_SIZE, +                          "OutgoingPacket type is not the right size"); +            static_assert(sizeof(ResponsePacket) == HID_REPORT_SIZE, +                          "ResponsePacket type is not the right size"); + +            static uint32_t getCRC( +                const command_payload &payload) { +              OutgoingPacket outp; +              outp.initialize(); +              outp.payload = payload; +              outp.update_CRC(); +              return outp.crc; +            } + +            template<typename T> +            static void clear_packet(T &st) { +              bzero(&st, sizeof(st)); +            } + + +            static ClearingProxy<ResponsePacket, response_payload> run(device::Device &dev, +                                                                       const command_payload &payload) { +              using namespace ::nitrokey::device; +              using namespace ::nitrokey::log; +              using namespace std::chrono_literals; + +              Log::instance()(__PRETTY_FUNCTION__, Loglevel::DEBUG_L2); + +              int status; +              OutgoingPacket outp; +              ResponsePacket resp; + +              // POD types can't have non-default constructors +              outp.initialize(); +              resp.initialize(); + +              outp.payload = payload; +              outp.update_CRC(); + +              Log::instance()("Outgoing HID packet:", Loglevel::DEBUG); +              Log::instance()(static_cast<std::string>(outp), Loglevel::DEBUG); + +              if (!outp.isValid()) throw std::runtime_error("Invalid outgoing packet"); + +              int receiving_retry_counter = 0; +              int sending_retry_counter = dev.get_retry_sending_count(); +              while (sending_retry_counter-- > 0) { +                status = dev.send(&outp); +                if (status <= 0) +                  throw std::runtime_error( +                      std::string("Device error while sending command ") + +                      std::to_string(status)); + +                std::this_thread::sleep_for(dev.get_send_receive_delay()); + +                // FIXME make checks done in device:recv here +                receiving_retry_counter = dev.get_retry_receiving_count(); +                while (receiving_retry_counter-- > 0) { +                  status = dev.recv(&resp); + +                  if (dev.get_device_model() == DeviceModel::STORAGE && +                      resp.command_id >= stick20::CMD_START_VALUE && +                      resp.command_id < stick20::CMD_END_VALUE ) { +                    Log::instance()(std::string("Detected storage device cmd, status: ") + +                                    std::to_string(resp.storage_status.device_status), Loglevel::DEBUG_L2); + +                    resp.last_command_status = static_cast<uint8_t>(stick10::command_status::ok); +                    switch (static_cast<stick20::device_status>(resp.storage_status.device_status)) { +                      case stick20::device_status::idle : +                      case stick20::device_status::ok: +                        resp.device_status = static_cast<uint8_t>(stick10::device_status::ok); +                        break; +                      case stick20::device_status::busy: +                      case stick20::device_status::busy_progressbar: //TODO this will be modified later for getting progressbar status +                        resp.device_status = static_cast<uint8_t>(stick10::device_status::busy); +                        break; +                      case stick20::device_status::wrong_password: +                        resp.last_command_status = static_cast<uint8_t>(stick10::command_status::wrong_password); +                        resp.device_status = static_cast<uint8_t>(stick10::device_status::ok); +                        break; +                      default: +                        Log::instance()(std::string("Unknown storage device status, cannot translate: ") + +                                        std::to_string(resp.storage_status.device_status), Loglevel::DEBUG); +                        resp.device_status = resp.storage_status.device_status; +                        break; +                    }; +                  } + +                  //SENDPASSWORD gives wrong CRC , for now rely on !=0 (TODO report) +//                  if (resp.device_status == 0 && resp.last_command_crc == outp.crc && resp.isCRCcorrect()) break; +                  if (resp.device_status == static_cast<uint8_t>(stick10::device_status::ok) && +                      resp.last_command_crc == outp.crc && resp.isValid()) break; +                  if (resp.device_status == static_cast<uint8_t>(stick10::device_status::busy)) { +                    receiving_retry_counter++; +                    Log::instance()("Status busy, not decresing receiving_retry_counter counter: " + +                                    std::to_string(receiving_retry_counter), Loglevel::DEBUG_L2); +                  } +                  Log::instance()(std::string("Retry status - dev status, equal crc, correct CRC: ") +                                  + std::to_string(resp.device_status) + " " + +                                  std::to_string(resp.last_command_crc == outp.crc) + +                                  " " + std::to_string(resp.isCRCcorrect()), Loglevel::DEBUG_L2); + +                  Log::instance()( +                      "Device is not ready or received packet's last CRC is not equal to sent CRC packet, retrying...",                        Loglevel::DEBUG); -      Log::instance()("Invalid incoming HID packet:", Loglevel::DEBUG_L2); -      Log::instance()((std::string)(resp), Loglevel::DEBUG_L2); -      std::this_thread::sleep_for(dev.get_retry_timeout()); -      continue; +                  Log::instance()("Invalid incoming HID packet:", Loglevel::DEBUG_L2); +                  Log::instance()(static_cast<std::string>(resp), Loglevel::DEBUG_L2); +                  std::this_thread::sleep_for(dev.get_retry_timeout()); +                  continue; +                } +                if (resp.device_status == 0 && resp.last_command_crc == outp.crc) break; +                Log::instance()(std::string("Resending (outer loop) "), Loglevel::DEBUG_L2); +                Log::instance()(std::string("sending_retry_counter count: ") + std::to_string(sending_retry_counter), +                                Loglevel::DEBUG); +              } + +              dev.set_last_command_status(resp.last_command_status); // FIXME should be handled on device.recv + +              clear_packet(outp); + +              if (status <= 0) +                throw std::runtime_error( +                    std::string("Device error while executing command ") + +                    std::to_string(status)); + +              Log::instance()("Incoming HID packet:", Loglevel::DEBUG); +              Log::instance()(static_cast<std::string>(resp), Loglevel::DEBUG); +              Log::instance()(std::string("receiving_retry_counter count: ") + std::to_string(receiving_retry_counter), +                              Loglevel::DEBUG); + +              if (!resp.isValid()) throw std::runtime_error("Invalid incoming packet"); +              if (receiving_retry_counter <= 0) +                throw std::runtime_error( +                    "Maximum receiving_retry_counter count reached for receiving response from the device!"); +              if (resp.last_command_status != static_cast<uint8_t>(stick10::command_status::ok)) +                throw CommandFailedException(resp.command_id, resp.last_command_status); + + +              // See: DeviceResponse +              return resp; +            } + +            static ClearingProxy<ResponsePacket, response_payload> run(device::Device &dev) { +              command_payload empty_payload; +              return run(dev, empty_payload); +            } +        };      } -    clear_packet(outp); - -    if (status <= 0) -      throw std::runtime_error( -          std::string("Device error while executing command ") + -          std::to_string(status)); - -    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 -    return resp; -  } - -  static ClearingProxy<ResponsePacket, response_payload> run(device::Device &dev) { -    command_payload empty_payload; -    return run(dev, empty_payload); -  } -}; -}  }  #endif diff --git a/include/dissect.h b/include/dissect.h index c83e648..59e6e9c 100644 --- a/include/dissect.h +++ b/include/dissect.h @@ -67,7 +67,7 @@ class ResponseDissector : semantics::non_constructible {      out << "Device status:\t" << pod.device_status + 0 << " "          << status[pod.device_status] << std::endl; -    out << "Command ID:\t" << commandid_to_string((CommandID)(pod.command_id)) +    out << "Command ID:\t" << commandid_to_string((CommandID)(pod.command_id)) << " hex: " << std::hex << (int)pod.command_id          << std::endl;      out << "Last command CRC:\t"              << std::hex << std::setw(2) << std::setfill('0') @@ -77,6 +77,14 @@ class ResponseDissector : semantics::non_constructible {      out << "CRC:\t"              << std::hex << std::setw(2) << std::setfill('0')              << pod.crc << std::endl; +      out << "Storage stick status:" << std::endl; +#define d(x) out << " "#x": \t"<< std::hex << std::setw(2) \ +    << std::setfill('0')<< static_cast<int>(x) << std::endl; +    d(pod.storage_status.command_counter); +    d(pod.storage_status.command_id); +    d(pod.storage_status.device_status); +    d(pod.storage_status.progress_bar_value); +#undef d      out << "Payload:" << std::endl;      out << pod.payload.dissect(); diff --git a/include/stick10_commands.h b/include/stick10_commands.h index a60be59..a947e1e 100644 --- a/include/stick10_commands.h +++ b/include/stick10_commands.h @@ -111,7 +111,8 @@ class WriteToHOTPSlot : Command<CommandID::WRITE_TO_SLOT> {          std::stringstream ss;          ss << "slot_number:\t" << (int)(slot_number) << std::endl;          ss << "slot_name:\t" << slot_name << std::endl; -        ss << "slot_secret:\t" << slot_secret << std::endl; +        ss << "slot_secret:" << std::endl +           << ::nitrokey::misc::hexdump((const char *)(&slot_secret), sizeof slot_secret);          ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl;          ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl;          ss << "\tuse_enter(1):\t" << use_enter << std::endl; @@ -121,8 +122,10 @@ class WriteToHOTPSlot : Command<CommandID::WRITE_TO_SLOT> {          for (auto i : slot_token_id)              ss << std::hex << std::setw(2) << std::setfill('0')<< (int) i << " " ;          ss << std::endl; -        ss << "slot_counter:\t" << (int)slot_counter << std::endl; -        return ss.str(); +        ss << "slot_counter:\t[" << (int)slot_counter << "]\t" +         << ::nitrokey::misc::hexdump((const char *)(&slot_counter), sizeof slot_counter, false); + +      return ss.str();      }    } __packed; @@ -331,7 +334,10 @@ class GetStatus : Command<CommandID::GET_STATUS> {      std::string dissect() const {        std::stringstream ss; -      ss << "firmware_version:\t" << firmware_version << std::endl; +      ss  << "firmware_version:\t" +          << "[" << firmware_version << "]" << "\t" +          << ::nitrokey::misc::hexdump( +          (const char *)(&firmware_version), sizeof firmware_version, false);        ss << "card_serial:\t"           << ::nitrokey::misc::hexdump((const char *)(card_serial),                                        sizeof card_serial, false); diff --git a/include/stick20_commands.h b/include/stick20_commands.h index c684e95..f4e7500 100644 --- a/include/stick20_commands.h +++ b/include/stick20_commands.h @@ -62,6 +62,26 @@ namespace stick20 {                  CommandTransaction;      }; + +    class UnlockUserPassword : Command<CommandID::UNLOCK_USER_PASSWORD> { +    public: +        struct CommandPayload { +            uint8_t kind; +            uint8_t user_new_password[20]; +            std::string dissect() const { +              std::stringstream ss; +              ss << " user_new_password:\t" <<  user_new_password<< std::endl; +              return ss.str(); +            } +            void set_kind(PasswordKind k){ +              kind = (uint8_t)k; +            } +        } __packed; + +        typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> +            CommandTransaction; +    }; +  class EnableEncryptedPartition : semantics::non_constructible {   public:    struct CommandPayload { @@ -126,15 +146,25 @@ class ExportFirmware : semantics::non_constructible {                        struct EmptyPayload> CommandTransaction;  }; -class CreateNewKeys : semantics::non_constructible { - public: -  struct CommandPayload { -    uint8_t password[30]; -  }; +    class CreateNewKeys : Command<CommandID::GENERATE_NEW_KEYS> { +    public: +        struct CommandPayload { +            uint8_t kind; +            uint8_t admin_password[30]; //CS20_MAX_PASSWORD_LEN +            std::string dissect() const { +              std::stringstream ss; +              ss << " admin_password:\t" <<  admin_password<< std::endl; +              return ss.str(); +            } +            void setKindPrefixed(){ +              kind = 'P'; +            } +        } __packed; + +        typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> +            CommandTransaction; +    }; -  typedef Transaction<CommandID::GENERATE_NEW_KEYS, struct CommandPayload, -                      struct EmptyPayload> CommandTransaction; -};  class FillSDCardWithRandomChars : semantics::non_constructible {   public: @@ -182,7 +212,7 @@ class SendPasswordMatrixSetup : semantics::non_constructible {                        struct EmptyPayload> CommandTransaction;  }; -#define d(x) ss << #x":\t" << x << std::endl; +#define d(x) ss << " "#x":\t" << (int)x << std::endl;      class GetDeviceStatus : Command<CommandID::GET_DEVICE_STATUS> {      public: @@ -190,7 +220,7 @@ class SendPasswordMatrixSetup : semantics::non_constructible {          static const int payload_absolute_begin = 8;          static const int padding_size = OUTPUT_CMD_RESULT_STICK20_STATUS_START - payload_absolute_begin;          struct ResponsePayload { -            uint8_t _padding[padding_size]; +            uint8_t _padding[padding_size]; //TODO confirm padding in Storage firmware              //data starts from 21st byte of packet -> 13th byte of payload              uint8_t command_counter;              uint8_t last_command; diff --git a/unittest/catch_main.cpp b/unittest/catch_main.cpp new file mode 100644 index 0000000..c8270db --- /dev/null +++ b/unittest/catch_main.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN  // This tells Catch to provide a main() +#include "catch.hpp"
\ No newline at end of file diff --git a/unittest/test_C_API.cpp b/unittest/test_C_API.cpp new file mode 100644 index 0000000..37d3c7f --- /dev/null +++ b/unittest/test_C_API.cpp @@ -0,0 +1,34 @@ +static const int TOO_LONG_STRING = 200; + +#include "catch.hpp" + +#include <iostream> +#include <string> +#include "log.h" +#include "../NK_C_API.h" + +TEST_CASE("C API connect", "[BASIC]") { +  auto login = NK_login_auto(); +  REQUIRE(login != 0); +  NK_logout(); +  login = NK_login_auto(); +  REQUIRE(login != 0); +  NK_logout(); +  login = NK_login_auto(); +  REQUIRE(login != 0); +} + +TEST_CASE("Check retry count", "[BASIC]") { +  REQUIRE(NK_get_admin_retry_count() == 3); +  REQUIRE(NK_get_user_retry_count() == 3); +} + +TEST_CASE("Check long strings", "[STANDARD]") { +  char* longPin = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; +  char *pin = "123123123"; +  auto result = NK_change_user_PIN(longPin, pin); +  REQUIRE(result == TOO_LONG_STRING); +  result = NK_change_user_PIN(pin, longPin); +  REQUIRE(result == TOO_LONG_STRING); +  CAPTURE(result); +}
\ No newline at end of file diff --git a/unittest/test_bindings.py b/unittest/test_bindings.py index 9c266aa..f7ade46 100644 --- a/unittest/test_bindings.py +++ b/unittest/test_bindings.py @@ -10,6 +10,13 @@ def to_hex(s):      return "".join("{:02x}".format(ord(c)) for c in s) +def wait(t): +    import time +    msg = 'Waiting for %d seconds' % t +    print(msg.center(40, '=')) +    time.sleep(t) + +  RFC_SECRET_HR = '12345678901234567890'  RFC_SECRET = to_hex(RFC_SECRET_HR)  # '12345678901234567890' @@ -78,6 +85,26 @@ def C(request):      return C +def get_firmware_version_from_status(C): +    status = gs(C.NK_status()) +    status = [s if 'firmware_version' in s else '' for s in status.split('\n')] +    firmware = status[0].split(':')[1] +    return firmware + + +def is_pro_rtm_07(C): +    firmware = get_firmware_version_from_status(C) +    return '07 00' in firmware + + +def is_storage(C): +    """ +    exact firmware storage is sent by other function +    """ +    firmware = get_firmware_version_from_status(C) +    return '01 00' in firmware + +  def test_enable_password_safe(C):      assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK      assert C.NK_enable_password_safe('wrong_password') == DeviceErrorCode.WRONG_PASSWORD @@ -134,9 +161,10 @@ def test_password_safe_slot_status(C):      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): +    if is_pro_rtm_07(C): +        pytest.skip("issue to register: device locks up " +                     "after below commands sequence (reinsertion fixes), skipping for now")      assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK      assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK @@ -148,7 +176,7 @@ def test_regenerate_aes_key(C):      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") +@pytest.mark.xfail(reason="NK Pro firmware bug: regenerating AES key command not always results in cleared slot data")  def test_destroy_password_safe(C):      """      Sometimes fails on NK Pro - slot name is not cleared ergo key generation has not succeed despite the success result @@ -182,6 +210,8 @@ def test_destroy_password_safe(C):  def test_is_AES_supported(C): +    if is_storage(C): +        pytest.skip("Storage does not implement this command")      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 @@ -237,6 +267,7 @@ def test_invalid_slot(C):  def test_admin_retry_counts(C):      default_admin_retry_count = 3 +    assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK      assert C.NK_get_admin_retry_count() == default_admin_retry_count      assert C.NK_change_admin_PIN('wrong_password', DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.WRONG_PASSWORD      assert C.NK_get_admin_retry_count() == default_admin_retry_count - 1 @@ -244,8 +275,20 @@ def test_admin_retry_counts(C):      assert C.NK_get_admin_retry_count() == default_admin_retry_count -def test_user_retry_counts(C): +def test_user_retry_counts_change_PIN(C): +    assert C.NK_change_user_PIN(DefaultPasswords.USER, DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK +    wrong_password = 'wrong_password' +    default_user_retry_count = 3 +    assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK +    assert C.NK_get_user_retry_count() == default_user_retry_count +    assert C.NK_change_user_PIN(wrong_password, wrong_password) == DeviceErrorCode.WRONG_PASSWORD +    assert C.NK_get_user_retry_count() == default_user_retry_count - 1 +    assert C.NK_change_user_PIN(DefaultPasswords.USER, DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK +    assert C.NK_get_user_retry_count() == default_user_retry_count + +def test_user_retry_counts_PWSafe(C):      default_user_retry_count = 3 +    assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK      assert C.NK_get_user_retry_count() == default_user_retry_count      assert C.NK_enable_password_safe('wrong_password') == DeviceErrorCode.WRONG_PASSWORD      assert C.NK_get_user_retry_count() == default_user_retry_count - 1 @@ -333,17 +376,91 @@ def test_HOTP_token(C):          assert hotp_code != 0          assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK +def test_HOTP_counters(C): +    """ +    # https://tools.ietf.org/html/rfc4226#page-32 +    """ +    use_pin_protection = False +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_write_config(255, 255, 255, use_pin_protection, not use_pin_protection, +                             DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    use_8_digits = True +    HOTP_test_data = [ +        1284755224, 1094287082, 137359152, 1726969429, 1640338314, +        868254676, 1918287922, 82162583, 673399871, 645520489, +    ] +    slot_number = 1 +    for counter, code in enumerate(HOTP_test_data): +        assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +        assert C.NK_write_hotp_slot(slot_number, 'python_test', RFC_SECRET, counter, use_8_digits, False, False, "", +                                    DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +        r = C.NK_get_hotp_code(slot_number) +        code = str(code)[-8:] if use_8_digits else str(code)[-6:] +        assert int(code) == r + -@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") +INT32_MAX = 2 ** 31 - 1 +def test_HOTP_64bit_counter(C): +    if is_storage(C): +        pytest.xfail('bug in NK Storage HOTP firmware - counter is set with a 8 digits string, ' +                     'however int32max takes 10 digits to be written') +    oath = pytest.importorskip("oath") +    lib_at = lambda t: oath.hotp(RFC_SECRET, t, format='dec6') +    PIN_protection = False +    use_8_digits = False +    slot_number = 1 +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection, +                             DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    dev_res = [] +    lib_res = [] +    for t in range(INT32_MAX - 5, INT32_MAX + 5, 1): +        assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +        assert C.NK_write_hotp_slot(slot_number, 'python_test', RFC_SECRET, t, use_8_digits, False, False, "", +                                    DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +        code_device = str(C.NK_get_hotp_code(slot_number)) +        dev_res += (t, code_device) +        lib_res += (t, lib_at(t)) +    assert dev_res == lib_res + + +def test_TOTP_64bit_time(C): +    if is_storage(C): +        pytest.xfail('bug in NK Storage TOTP firmware') +    oath = pytest.importorskip("oath") +    T = 1 +    lib_at = lambda t: oath.totp(RFC_SECRET, t=t) +    PIN_protection = False +    slot_number = 1 +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection, +                             DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_write_totp_slot(slot_number, 'python_test', RFC_SECRET, 30, False, False, False, "", +                                DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    dev_res = [] +    lib_res = [] +    for t in range(INT32_MAX - 5, INT32_MAX + 5, 1): +        assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +        assert C.NK_totp_set_time(t) == DeviceErrorCode.STATUS_OK +        code_device = str((C.NK_get_totp_code(slot_number, T, 0, 30))) +        dev_res += (t, code_device) +        lib_res += (t, lib_at(t)) +    assert dev_res == lib_res + + +@pytest.mark.xfail(reason="NK Pro: possible firmware bug or communication issue: set time command not always changes the time on stick thus failing this test, " +                          "this does not influence normal use since setting time is not done every TOTP code request" +                          "Rarely fail occurs on NK Storage")  @pytest.mark.parametrize("PIN_protection", [False, True, ])  def test_TOTP_RFC_usepin(C, PIN_protection): +    slot_number = 1      assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK      assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection,                               DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK      # test according to https://tools.ietf.org/html/rfc6238#appendix-B      assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK -    assert C.NK_write_totp_slot(1, 'python_test', RFC_SECRET, 30, True, False, False, "", +    assert C.NK_write_totp_slot(slot_number, 'python_test', RFC_SECRET, 30, True, False, False, "",                                  DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK      get_func = None @@ -352,26 +469,29 @@ def test_TOTP_RFC_usepin(C, PIN_protection):      else:          get_func = C.NK_get_totp_code +    # Mode: Sha1, time step X=30      test_data = [ -        (59, 1, 94287082), -        (1111111109, 0x00000000023523EC, 7081804), -        (1111111111, 0x00000000023523ED, 14050471), -        (1234567890, 0x000000000273EF07, 89005924), +        #Time         T (hex)               TOTP +        (59,          0x1,                94287082), +        (1111111109,  0x00000000023523EC, 7081804), +        (1111111111,  0x00000000023523ED, 14050471), +        (1234567890,  0x000000000273EF07, 89005924), +        (2000000000,  0x0000000003F940AA, 69279037), +        # (20000000000, 0x0000000027BC86AA, 65353130), # 64bit is also checked in other test      ] -    for t, T, code in test_data: -        """ -        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) +    responses = [] +    data = [] +    correct = 0 +    for t, T, expected_code in test_data:          if PIN_protection:              C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP)          assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK          assert C.NK_totp_set_time(t) == DeviceErrorCode.STATUS_OK -        r = get_func(1, T, 0, 30)  # FIXME T is not changing the outcome -        assert code == r - +        code_from_device = get_func(slot_number, T, 0, 30)  # FIXME T is not changing the outcome +        data += [ (t, expected_code) ] +        responses += [ (t, code_from_device) ] +        correct += expected_code == code_from_device +    assert data == responses or correct == len(test_data)  def test_get_slot_names(C):      C.NK_set_debug(True) @@ -468,14 +588,9 @@ def test_read_write_config(C):      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) - -  def test_factory_reset(C): +    if is_storage(C): +        pytest.skip('Recovery not implemented for NK Storage')      C.NK_set_debug(True)      assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK      assert C.NK_write_config(255, 255, 255, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK @@ -532,3 +647,8 @@ def test_warning_binary_bigger_than_secret_buffer(C):      invalid_hex_string = to_hex('1234567890') * 3      assert C.NK_write_hotp_slot(1, 'slot_name', invalid_hex_string, 0, True, False, False, '',                                  DefaultPasswords.ADMIN_TEMP) == LibraryErrors.TARGET_BUFFER_SIZE_SMALLER_THAN_SOURCE + + +@pytest.mark.xfail(reason="TODO") +def test_OTP_secret_started_from_null(C): +    assert False | 
