diff options
| author | szszszsz <szszszsz@users.noreply.github.com> | 2016-12-12 17:06:35 +0100 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2016-12-12 17:06:35 +0100 | 
| commit | ed5044da43172d86a1aa475473561a4818b7c69c (patch) | |
| tree | a6d3775f20ac86e7cdbbc151e0f51620d1399e56 | |
| parent | f60f2cf0144a91769a5fc00fac1314d2e00cdf0d (diff) | |
| parent | e26c6da38c674d8ec37e402132dab823bd22bd36 (diff) | |
| download | libnitrokey-ed5044da43172d86a1aa475473561a4818b7c69c.tar.gz libnitrokey-ed5044da43172d86a1aa475473561a4818b7c69c.tar.bz2 | |
Merge pull request #53 from Nitrokey/nk_pro_0.8_authorization_fix-longer_secretv2.0
Support for Nitrokey Pro 0.8
| -rw-r--r-- | CMakeLists.txt | 2 | ||||
| -rw-r--r-- | NK_C_API.cc | 7 | ||||
| -rw-r--r-- | NK_C_API.h | 6 | ||||
| -rw-r--r-- | NitrokeyManager.cc | 237 | ||||
| -rw-r--r-- | command_id.cc | 6 | ||||
| -rw-r--r-- | include/NitrokeyManager.h | 20 | ||||
| -rw-r--r-- | include/command_id.h | 2 | ||||
| -rw-r--r-- | include/device.h | 9 | ||||
| -rw-r--r-- | include/dissect.h | 59 | ||||
| -rw-r--r-- | include/stick10_commands_0.8.h | 312 | ||||
| -rw-r--r-- | include/stick20_commands.h | 10 | ||||
| -rw-r--r-- | misc.cc | 7 | ||||
| -rw-r--r-- | unittest/conftest.py | 16 | ||||
| -rw-r--r-- | unittest/constants.py | 2 | ||||
| -rw-r--r-- | unittest/misc.py | 24 | ||||
| -rw-r--r-- | unittest/requirements.txt | 4 | ||||
| -rw-r--r-- | unittest/test3.cc | 220 | ||||
| -rw-r--r-- | unittest/test_library.py | 15 | ||||
| -rw-r--r-- | unittest/test_pro.py | 276 | ||||
| -rw-r--r-- | unittest/test_storage.py | 22 | 
20 files changed, 1149 insertions, 107 deletions
| diff --git a/CMakeLists.txt b/CMakeLists.txt index e9cd54f..c324067 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,9 @@ set(SOURCE_FILES          unittest/test_C_API.cpp          unittest/catch_main.cpp          unittest/test2.cc +        unittest/test3.cc          include/LongOperationInProgressException.h +        include/stick10_commands_0.8.h          )  add_executable(libnitrokey ${SOURCE_FILES})
\ No newline at end of file diff --git a/NK_C_API.cc b/NK_C_API.cc index d42840b..e513a3b 100644 --- a/NK_C_API.cc +++ b/NK_C_API.cc @@ -471,6 +471,13 @@ extern int NK_get_progress_bar_value() {    });  } +extern int NK_get_major_firmware_version(){ +  auto m = NitrokeyManager::instance(); +  return get_with_result([&](){ +      return m->get_major_firmware_version(); +  }); +} +  } @@ -324,7 +324,11 @@ extern int NK_erase_password_safe_slot(uint8_t slot_number);   */  extern int NK_is_AES_supported(const char *user_password); - +/** + * Get device's major firmware version + * @return 7,8 for Pro and major for Storage + */ +extern int NK_get_major_firmware_version(); diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index 20f4f14..ddec600 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -3,6 +3,8 @@  #include "include/NitrokeyManager.h"  #include "include/LibraryException.h"  #include <algorithm> +#include <unordered_map> +#include <stick20_commands.h>  #include "include/misc.h"  namespace nitrokey{ @@ -35,7 +37,10 @@ namespace nitrokey{      // package type to auth, auth type [Authorize,UserAuthorize]      template <typename S, typename A, typename T> -    void authorize_packet(T &package, const char *admin_temporary_password, shared_ptr<Device> device){ +    void NitrokeyManager::authorize_packet(T &package, const char *admin_temporary_password, shared_ptr<Device> device){ +      if (!is_authorization_command_supported()){ +        Log::instance()("Authorization command not supported, skipping", Loglevel::WARNING); +      }          auto auth = get_payload<A>();          strcpyT(auth.temporary_password, admin_temporary_password);          auth.crc_to_authorize = S::CommandTransaction::getCRC(package); @@ -112,16 +117,25 @@ namespace nitrokey{      }      uint32_t NitrokeyManager::get_HOTP_code(uint8_t slot_number, const char *user_temporary_password) { -        if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number); +      if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number); + +      if (is_authorization_command_supported()){          auto gh = get_payload<GetHOTP>();          gh.slot_number = get_internal_slot_number_for_hotp(slot_number); -          if(user_temporary_password != nullptr && strlen(user_temporary_password)!=0){ //FIXME use string instead of strlen              authorize_packet<GetHOTP, UserAuthorize>(gh, user_temporary_password, device);          } -          auto resp = GetHOTP::CommandTransaction::run(*device, gh);          return resp.data().code; +      } else { +        auto gh = get_payload<stick10_08::GetHOTP>(); +        gh.slot_number = get_internal_slot_number_for_hotp(slot_number); +        if(user_temporary_password != nullptr && strlen(user_temporary_password)!=0) { +          strcpyT(gh.temporary_user_password, user_temporary_password); +        } +        auto resp = stick10_08::GetHOTP::CommandTransaction::run(*device, gh); +        return resp.data().code; +      }      } @@ -135,26 +149,41 @@ namespace nitrokey{                                              const char *user_temporary_password) {          if(!is_valid_totp_slot_number(slot_number)) throw InvalidSlotException(slot_number);          slot_number = get_internal_slot_number_for_totp(slot_number); -        auto gt = get_payload<GetTOTP>(); -        gt.slot_number = slot_number; -        gt.challenge = challenge; -        gt.last_interval = last_interval; -        gt.last_totp_time = last_totp_time; -        if(user_temporary_password != nullptr && strlen(user_temporary_password)!=0){ //FIXME use string instead of strlen -            authorize_packet<GetTOTP, UserAuthorize>(gt, user_temporary_password, device); +        if (is_authorization_command_supported()){ +          auto gt = get_payload<GetTOTP>(); +          gt.slot_number = slot_number; +          gt.challenge = challenge; +          gt.last_interval = last_interval; +          gt.last_totp_time = last_totp_time; + +          if(user_temporary_password != nullptr && strlen(user_temporary_password)!=0){ //FIXME use string instead of strlen +              authorize_packet<GetTOTP, UserAuthorize>(gt, user_temporary_password, device); +          } +          auto resp = GetTOTP::CommandTransaction::run(*device, gt); +          return resp.data().code; +        } else { +          auto gt = get_payload<stick10_08::GetTOTP>(); +          strcpyT(gt.temporary_user_password, user_temporary_password); +          gt.slot_number = slot_number; +          auto resp = stick10_08::GetTOTP::CommandTransaction::run(*device, gt); +          return resp.data().code;          } -        auto resp = GetTOTP::CommandTransaction::run(*device, gt); -        return resp.data().code; +      }      bool NitrokeyManager::erase_slot(uint8_t slot_number, const char *temporary_password) { +      if (is_authorization_command_supported()){          auto p = get_payload<EraseSlot>();          p.slot_number = slot_number; -          authorize_packet<EraseSlot, Authorize>(p, temporary_password, device); -          auto resp = EraseSlot::CommandTransaction::run(*device,p); +      } else { +        auto p = get_payload<stick10_08::EraseSlot>(); +        p.slot_number = slot_number; +        strcpyT(p.temporary_admin_password, temporary_password); +        auto resp = stick10_08::EraseSlot::CommandTransaction::run(*device,p); +      }          return true;      } @@ -171,6 +200,16 @@ namespace nitrokey{      }      template <typename T, typename U> +    void vector_copy_ranged(T& dest, std::vector<U> &vec, size_t begin, size_t elements_to_copy){ +        const size_t d_size = sizeof(dest); +      if(d_size < elements_to_copy){ +            throw TargetBufferSmallerThanSource(elements_to_copy, d_size); +        } +        std::fill(dest, dest+d_size, 0); +        std::copy(vec.begin() + begin, vec.begin() +begin + elements_to_copy, dest); +    } + +    template <typename T, typename U>      void vector_copy(T& dest, std::vector<U> &vec){          const size_t d_size = sizeof(dest);          if(d_size < vec.size()){ @@ -185,60 +224,127 @@ namespace nitrokey{                                            const char *temporary_password) {          if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number); -        slot_number = get_internal_slot_number_for_hotp(slot_number); -        auto payload = get_payload<WriteToHOTPSlot>(); -        payload.slot_number = slot_number; -        auto secret_bin = misc::hex_string_to_byte(secret); -        vector_copy(payload.slot_secret, secret_bin); -        strcpyT(payload.slot_name, slot_name); -        strcpyT(payload.slot_token_id, token_ID); +      int internal_slot_number = get_internal_slot_number_for_hotp(slot_number); +      if (is_authorization_command_supported()){ +        write_HOTP_slot_authorize(internal_slot_number, slot_name, secret, hotp_counter, use_8_digits, use_enter, use_tokenID, +                    token_ID, temporary_password); +      } else { +        write_OTP_slot_no_authorize(internal_slot_number, slot_name, secret, hotp_counter, use_8_digits, use_enter, use_tokenID, +                                    token_ID, temporary_password); +      } +      return true; +    } + +    void NitrokeyManager::write_HOTP_slot_authorize(uint8_t slot_number, const char *slot_name, const char *secret, +                                                    uint64_t hotp_counter, bool use_8_digits, bool use_enter, +                                                    bool use_tokenID, const char *token_ID, const char *temporary_password) { +      auto payload = get_payload<WriteToHOTPSlot>(); +      payload.slot_number = slot_number; +      auto secret_bin = misc::hex_string_to_byte(secret); +      vector_copy(payload.slot_secret, secret_bin); +      strcpyT(payload.slot_name, slot_name); +      strcpyT(payload.slot_token_id, token_ID);        switch (device->get_device_model() ){          case DeviceModel::PRO: {            payload.slot_counter = hotp_counter;            break;          }          case DeviceModel::STORAGE: { -          std::string counter = std::to_string(hotp_counter); +          string counter = to_string(hotp_counter);            strcpyT(payload.slot_counter_s, counter.c_str());            break;          }          default: -          nitrokey::log::Log::instance()(  std::string(__FILE__) + std::to_string(__LINE__) + -                   std::string(__FUNCTION__) + std::string(" Unhandled device model for HOTP") -              , nitrokey::log::Loglevel::DEBUG); +          Log::instance()(string(__FILE__) + to_string(__LINE__) + +                          string(__FUNCTION__) + string(" Unhandled device model for HOTP") +              , Loglevel::DEBUG);            break;        } -        payload.use_8_digits = use_8_digits; -        payload.use_enter = use_enter; -        payload.use_tokenID = use_tokenID; +      payload.use_8_digits = use_8_digits; +      payload.use_enter = use_enter; +      payload.use_tokenID = use_tokenID; -        authorize_packet<WriteToHOTPSlot, Authorize>(payload, temporary_password, device); +      authorize_packet<WriteToHOTPSlot, Authorize>(payload, temporary_password, device); -        auto resp = WriteToHOTPSlot::CommandTransaction::run(*device, payload); -        return true; +      auto resp = WriteToHOTPSlot::CommandTransaction::run(*device, payload);      }      bool NitrokeyManager::write_TOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window,                                                bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,                                                const char *temporary_password) { -        auto payload = get_payload<WriteToTOTPSlot>();          if (!is_valid_totp_slot_number(slot_number)) throw InvalidSlotException(slot_number); +       int internal_slot_number = get_internal_slot_number_for_totp(slot_number); + +      if (is_authorization_command_supported()){ +      write_TOTP_slot_authorize(internal_slot_number, slot_name, secret, time_window, use_8_digits, use_enter, use_tokenID, +                                token_ID, temporary_password); +      } else { +        write_OTP_slot_no_authorize(internal_slot_number, slot_name, secret, time_window, use_8_digits, use_enter, use_tokenID, +                                    token_ID, temporary_password); +      } -        slot_number = get_internal_slot_number_for_totp(slot_number); -        payload.slot_number = slot_number; -        auto secret_bin = misc::hex_string_to_byte(secret); -        vector_copy(payload.slot_secret, secret_bin); -        strcpyT(payload.slot_name, slot_name); -        strcpyT(payload.slot_token_id, token_ID); -        payload.slot_interval = time_window; //FIXME naming -        payload.use_8_digits = use_8_digits; -        payload.use_enter = use_enter; -        payload.use_tokenID = use_tokenID; - -        authorize_packet<WriteToTOTPSlot, Authorize>(payload, temporary_password, device); - -        auto resp = WriteToTOTPSlot::CommandTransaction::run(*device, payload); -        return true; +      return true; +    } + +    void NitrokeyManager::write_OTP_slot_no_authorize(uint8_t internal_slot_number, const char *slot_name, +                                                      const char *secret, +                                                      uint64_t counter_or_interval, bool use_8_digits, bool use_enter, +                                                      bool use_tokenID, const char *token_ID, +                                                      const char *temporary_password) const { + +      auto payload2 = get_payload<stick10_08::SendOTPData>(); +      strcpyT(payload2.temporary_admin_password, temporary_password); +      strcpyT(payload2.data, slot_name); +      payload2.setTypeName(); +      stick10_08::SendOTPData::CommandTransaction::run(*device, payload2); + +      payload2.setTypeSecret(); +      payload2.id = 0; +      auto secret_bin = misc::hex_string_to_byte(secret); +      auto remaining_secret_length = secret_bin.size(); +      const auto maximum_OTP_secret_size = 40; +      if(remaining_secret_length > maximum_OTP_secret_size){ +        throw TargetBufferSmallerThanSource(remaining_secret_length, maximum_OTP_secret_size); +      } + +      while (remaining_secret_length>0){ +        const auto bytesToCopy = std::min(sizeof(payload2.data), remaining_secret_length); +        const auto start = secret_bin.size() - remaining_secret_length; +        memset(payload2.data, 0, sizeof(payload2.data)); +        vector_copy_ranged(payload2.data, secret_bin, start, bytesToCopy); +        stick10_08::SendOTPData::CommandTransaction::run(*device, payload2); +        remaining_secret_length -= bytesToCopy; +        payload2.id++; +      } + +      auto payload = get_payload<stick10_08::WriteToOTPSlot>(); +      strcpyT(payload.temporary_admin_password, temporary_password); +      strcpyT(payload.slot_token_id, token_ID); +      payload.use_8_digits = use_8_digits; +      payload.use_enter = use_enter; +      payload.use_tokenID = use_tokenID; +      payload.slot_counter_or_interval = counter_or_interval; +      payload.slot_number = internal_slot_number; +      stick10_08::WriteToOTPSlot::CommandTransaction::run(*device, payload); +    } + +    void NitrokeyManager::write_TOTP_slot_authorize(uint8_t slot_number, const char *slot_name, const char *secret, +                                                    uint16_t time_window, bool use_8_digits, bool use_enter, +                                                    bool use_tokenID, const char *token_ID, const char *temporary_password) { +      auto payload = get_payload<WriteToTOTPSlot>(); +      payload.slot_number = slot_number; +      auto secret_bin = misc::hex_string_to_byte(secret); +      vector_copy(payload.slot_secret, secret_bin); +      strcpyT(payload.slot_name, slot_name); +      strcpyT(payload.slot_token_id, token_ID); +      payload.slot_interval = time_window; //FIXME naming +      payload.use_8_digits = use_8_digits; +      payload.use_enter = use_enter; +      payload.use_tokenID = use_tokenID; + +      authorize_packet<WriteToTOTPSlot, Authorize>(payload, temporary_password, device); + +      auto resp = WriteToTOTPSlot::CommandTransaction::run(*device, payload);      }      const char * NitrokeyManager::get_totp_slot_name(uint8_t slot_number) { @@ -461,16 +567,18 @@ namespace nitrokey{      void NitrokeyManager::write_config(uint8_t numlock, uint8_t capslock, uint8_t scrolllock, bool enable_user_password,                                         bool delete_user_password, const char *admin_temporary_password) { -        auto p = get_payload<WriteGeneralConfig>(); +        auto p = get_payload<stick10_08::WriteGeneralConfig>();          p.numlock = (uint8_t) numlock;          p.capslock = (uint8_t) capslock;          p.scrolllock = (uint8_t) scrolllock;          p.enable_user_password = (uint8_t) enable_user_password;          p.delete_user_password = (uint8_t) delete_user_password; - -        authorize_packet<WriteGeneralConfig, Authorize>(p, admin_temporary_password, device); - -        WriteGeneralConfig::CommandTransaction::run(*device, p); +        if (is_authorization_command_supported()){ +          authorize_packet<stick10_08::WriteGeneralConfig, Authorize>(p, admin_temporary_password, device); +        } else { +          strcpyT(p.temporary_admin_password, admin_temporary_password); +        } +        stick10_08::WriteGeneralConfig::CommandTransaction::run(*device, p);      }      vector<uint8_t> NitrokeyManager::read_config() { @@ -480,6 +588,29 @@ namespace nitrokey{          return v;      } +    bool NitrokeyManager::is_authorization_command_supported(){ +      //authorization command is supported for versions equal or below: +        auto m = std::unordered_map<DeviceModel , int, EnumClassHash>({ +                                               {DeviceModel::PRO, 7}, +                                               {DeviceModel::STORAGE, 43}, +         }); +        return get_major_firmware_version() <= m[device->get_device_model()]; +    } + +    int NitrokeyManager::get_major_firmware_version(){ +      switch(device->get_device_model()){ +        case DeviceModel::PRO:{ +          auto status_p = GetStatus::CommandTransaction::run(*device); +          return status_p.data().firmware_version; //7 or 8 +        } +        case DeviceModel::STORAGE:{ +          auto status = stick20::GetDeviceStatus::CommandTransaction::run(*device); +          return status.data().versionInfo.major; +        } +      } +      return 0; +    } +      bool NitrokeyManager::is_AES_supported(const char *user_password) {          auto a = get_payload<IsAESSupported>();          strcpyT(a.user_password, user_password); diff --git a/command_id.cc b/command_id.cc index 9512b7d..f76a358 100644 --- a/command_id.cc +++ b/command_id.cc @@ -139,6 +139,12 @@ const char *commandid_to_string(CommandID id) {        return "DETECT_SC_AES";      case CommandID::NEW_AES_KEY:        return "NEW_AES_KEY"; +    case CommandID::WRITE_TO_SLOT_2: +      return "WRITE_TO_SLOT_2"; +      break; +    case CommandID::SEND_OTP_DATA: +      return "SEND_OTP_DATA"; +      break;    }    return "UNKNOWN";  } diff --git a/include/NitrokeyManager.h b/include/NitrokeyManager.h index 60fa753..fd39445 100644 --- a/include/NitrokeyManager.h +++ b/include/NitrokeyManager.h @@ -5,6 +5,7 @@  #include "log.h"  #include "device_proto.h"  #include "stick10_commands.h" +#include "stick10_commands_0.8.h"  #include "stick20_commands.h"  #include <vector>  #include <memory> @@ -110,6 +111,12 @@ namespace nitrokey {          int get_progress_bar_value();          ~NitrokeyManager(); +        bool is_authorization_command_supported(); + +        template <typename S, typename A, typename T> +        void authorize_packet(T &package, const char *admin_temporary_password, shared_ptr<Device> device); +        int get_major_firmware_version(); +      private:          NitrokeyManager(); @@ -128,6 +135,19 @@ namespace nitrokey {          template <typename ProCommand, PasswordKind StoKind>          void change_PIN_general(char *current_PIN, char *new_PIN); +        void write_HOTP_slot_authorize(uint8_t slot_number, const char *slot_name, const char *secret, uint64_t hotp_counter, +                                   bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, +                                   const char *temporary_password); + +        void write_TOTP_slot_authorize(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window, +                                   bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, +                                   const char *temporary_password); + +        void write_OTP_slot_no_authorize(uint8_t internal_slot_number, const char *slot_name, const char *secret, +                                         uint64_t counter_or_interval, +                                         bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID, +                                         const char *temporary_password) const; +      };  } diff --git a/include/command_id.h b/include/command_id.h index a3806f0..346b750 100644 --- a/include/command_id.h +++ b/include/command_id.h @@ -65,6 +65,8 @@ enum class CommandID : uint8_t {    FACTORY_RESET = 0x13,    CHANGE_USER_PIN = 0x14,    CHANGE_ADMIN_PIN = 0x15, +  WRITE_TO_SLOT_2 = 0x16, +  SEND_OTP_DATA = 0x17,    ENABLE_CRYPTED_PARI = 0x20,    DISABLE_CRYPTED_PARI = 0x20 + 1, //@unused diff --git a/include/device.h b/include/device.h index 3f18921..62c4073 100644 --- a/include/device.h +++ b/include/device.h @@ -12,6 +12,15 @@ namespace nitrokey {  namespace device {      using namespace std::chrono_literals; +    struct EnumClassHash +    { +        template <typename T> +        std::size_t operator()(T t) const +        { +          return static_cast<std::size_t>(t); +        } +    }; +  enum class DeviceModel{      PRO,      STORAGE diff --git a/include/dissect.h b/include/dissect.h index 59e6e9c..8c975c5 100644 --- a/include/dissect.h +++ b/include/dissect.h @@ -36,44 +36,65 @@ class QueryDissector : semantics::non_constructible {    }  }; + + +  template <CommandID id, class HIDPacket>  class ResponseDissector : semantics::non_constructible {   public: +    static std::string status_translate_device(int status){ +      auto enum_status = static_cast<proto::stick10::device_status>(status); +      switch (enum_status){ +        case stick10::device_status::ok: return "OK"; +        case stick10::device_status::busy: return "BUSY"; +        case stick10::device_status::error: return "ERROR"; +        case stick10::device_status::received_report: return "RECEIVED_REPORT"; +      } +      return std::string("UNKNOWN: ") + std::to_string(status); +    } + +    static std::string to_upper(std::string str){ +        for (auto & c: str) c = toupper(c); +      return str; +    } +    static std::string status_translate_command(int status){ +      auto enum_status = static_cast<proto::stick10::command_status >(status); +      switch (enum_status) { +#define p(X) case X: return to_upper(std::string(#X)); +        p(stick10::command_status::ok) +        p(stick10::command_status::wrong_CRC) +        p(stick10::command_status::wrong_slot) +        p(stick10::command_status::slot_not_programmed) +        p(stick10::command_status::wrong_password) +        p(stick10::command_status::not_authorized) +        p(stick10::command_status::timestamp_warning) +        p(stick10::command_status::no_name_error) +        p(stick10::command_status::not_supported) +        p(stick10::command_status::unknown_command) +        p(stick10::command_status::AES_dec_failed) +#undef p +      } +      return std::string("UNKNOWN: ") + std::to_string(status); +    } +    static std::string dissect(const HIDPacket &pod) {      std::stringstream out;      // FIXME use values from firmware (possibly generate separate      // header automatically) -    std::string status[4]; -    status[0] = " STATUS_READY"; -    status[1] = " STATUS_BUSY"; -    status[2] = " STATUS_ERROR"; -    status[3] = " STATUS_RECEIVED_REPORT"; -    std::string cmd[11]; -    cmd[0] = " CMD_STATUS_OK"; -    cmd[1] = " CMD_STATUS_WRONG_CRC"; -    cmd[2] = " CMD_STATUS_WRONG_SLOT"; -    cmd[3] = " CMD_STATUS_SLOT_NOT_PROGRAMMED"; -    cmd[4] = " CMD_STATUS_WRONG_PASSWORD"; -    cmd[5] = " CMD_STATUS_NOT_AUTHORIZED"; -    cmd[6] = " CMD_STATUS_TIMESTAMP_WARNING"; -    cmd[7] = " CMD_STATUS_NO_NAME_ERROR"; -    cmd[8] = " CMD_STATUS_NOT_SUPPORTED"; -    cmd[9] = " CMD_STATUS_UNKNOWN_COMMAND"; -    cmd[10] = " CMD_STATUS_AES_DEC_FAILED";      out << "Raw HID packet:" << std::endl;      out << ::nitrokey::misc::hexdump((const char *)(&pod), sizeof pod);      out << "Device status:\t" << pod.device_status + 0 << " " -        << status[pod.device_status] << std::endl; +        << status_translate_device(pod.device_status) << std::endl;      out << "Command ID:\t" << commandid_to_string((CommandID)(pod.command_id)) << " hex: " << std::hex << (int)pod.command_id          << std::endl;      out << "Last command CRC:\t"              << std::hex << std::setw(2) << std::setfill('0')              << pod.last_command_crc << std::endl;      out << "Last command status:\t" << pod.last_command_status + 0 << " " -        << cmd[pod.last_command_status] << std::endl; +        << status_translate_command(pod.last_command_status) << std::endl;      out << "CRC:\t"              << std::hex << std::setw(2) << std::setfill('0')              << pod.crc << std::endl; diff --git a/include/stick10_commands_0.8.h b/include/stick10_commands_0.8.h new file mode 100644 index 0000000..9594d1e --- /dev/null +++ b/include/stick10_commands_0.8.h @@ -0,0 +1,312 @@ +// +// Created by sz on 08.11.16. +// + +#ifndef LIBNITROKEY_STICK10_COMMANDS_0_8_H +#define LIBNITROKEY_STICK10_COMMANDS_0_8_H + +#include <bitset> +#include <iomanip> +#include <string> +#include <sstream> +#include "inttypes.h" +#include "command.h" +#include "device_proto.h" +#include "stick10_commands.h" + +namespace nitrokey { +    namespace proto { + +/* + *	Stick10 protocol definition + */ +        namespace stick10_08 { +            using stick10::FirstAuthenticate; +            using stick10::UserAuthenticate; +            using stick10::SetTime; +            using stick10::GetStatus; +            using stick10::BuildAESKey; +            using stick10::ChangeAdminPin; +            using stick10::ChangeUserPin; +            using stick10::EnablePasswordSafe; +            using stick10::ErasePasswordSafeSlot; +            using stick10::FactoryReset; +            using stick10::GetPasswordRetryCount; +            using stick10::GetUserPasswordRetryCount; +            using stick10::GetPasswordSafeSlotLogin; +            using stick10::GetPasswordSafeSlotName; +            using stick10::GetPasswordSafeSlotPassword; +            using stick10::GetPasswordSafeSlotStatus; +            using stick10::GetSlotName; +            using stick10::IsAESSupported; +            using stick10::LockDevice; +            using stick10::PasswordSafeInitKey; +            using stick10::PasswordSafeSendSlotViaHID; +            using stick10::SetPasswordSafeSlotData; +            using stick10::SetPasswordSafeSlotData2; +            using stick10::UnlockUserPassword; +            using stick10::ReadSlot; + +            class EraseSlot : Command<CommandID::ERASE_SLOT> { +            public: +                struct CommandPayload { +                    uint8_t slot_number; +                    uint8_t temporary_admin_password[25]; + +                    bool isValid() const { return !(slot_number & 0xF0); } +                    std::string dissect() const { +                      std::stringstream ss; +                      ss << "slot_number:\t" << (int)(slot_number) << std::endl; +                      return ss.str(); +                    } +                } __packed; + +                typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> +                    CommandTransaction; +            }; + +            class SendOTPData : Command<CommandID::SEND_OTP_DATA> { +                //admin auth +            public: +                struct CommandPayload { +                    uint8_t temporary_admin_password[25]; +                    uint8_t type; //S-secret, N-name +                    uint8_t id; //multiple reports for values longer than 30 bytes +                    uint8_t data[30]; //data, does not need null termination + +                    bool isValid() const { return true; } + +                    void setTypeName(){ +                      type = 'N'; +                    } +                    void setTypeSecret(){ +                      type = 'S'; +                    } + +                    std::string dissect() const { +                      std::stringstream ss; +                      ss << "temporary_admin_password:\t" << temporary_admin_password << std::endl; +                      ss << "type:\t" << type << std::endl; +                      ss << "id:\t" << (int)id << std::endl; +                      ss << "data:" << std::endl +                         << ::nitrokey::misc::hexdump((const char *) (&data), sizeof data); +                      return ss.str(); +                    } +                } __packed; + + +                struct ResponsePayload { +                    union { +                        uint8_t data[40]; +                    } __packed; + +                    bool isValid() const { return true; } +                    std::string dissect() const { +                      std::stringstream ss; +                      ss << "data:" << std::endl +                         << ::nitrokey::misc::hexdump((const char *) (&data), sizeof data); +                      return ss.str(); +                    } +                } __packed; + + +                typedef Transaction<command_id(), struct CommandPayload, struct ResponsePayload> +                    CommandTransaction; +            }; + +            class WriteToOTPSlot : Command<CommandID::WRITE_TO_SLOT> { +                //admin auth +            public: +                struct CommandPayload { +                    uint8_t temporary_admin_password[25]; +                    uint8_t slot_number; +                    union { +                        uint64_t slot_counter_or_interval; +                        uint8_t slot_counter_s[8]; +                    } __packed; +                    union { +                        uint8_t _slot_config; +                        struct { +                            bool use_8_digits   : 1; +                            bool use_enter      : 1; +                            bool use_tokenID    : 1; +                        }; +                    }; +                    union { +                        uint8_t slot_token_id[13]; /** OATH Token Identifier */ +                        struct { /** @see https://openauthentication.org/token-specs/ */ +                            uint8_t omp[2]; +                            uint8_t tt[2]; +                            uint8_t mui[8]; +                            uint8_t keyboard_layout; //disabled feature in nitroapp as of 20160805 +                        } slot_token_fields; +                    }; + +                    bool isValid() const { return true; } + +                    std::string dissect() const { +                      std::stringstream ss; +                      ss << "temporary_admin_password:\t" << temporary_admin_password << std::endl; +                      ss << "slot_config:\t" << std::bitset<8>((int) _slot_config) << std::endl; +                      ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl; +                      ss << "\tuse_enter(1):\t" << use_enter << std::endl; +                      ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl; +                      ss << "slot_number:\t" << (int) (slot_number) << std::endl; +                      ss << "slot_counter_or_interval:\t[" << (int) slot_counter_or_interval << "]\t" +                         << ::nitrokey::misc::hexdump((const char *) (&slot_counter_or_interval), sizeof slot_counter_or_interval, false); + +                      ss << "slot_token_id:\t"; +                      for (auto i : slot_token_id) +                        ss << std::hex << std::setw(2) << std::setfill('0') << (int) i << " "; +                      ss << std::endl; + +                      return ss.str(); +                    } +                } __packed; + +                typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> +                    CommandTransaction; +            }; + +            class GetHOTP : Command<CommandID::GET_CODE> { +            public: +                struct CommandPayload { +                    uint8_t slot_number; +                    struct { +                        uint64_t challenge; //@unused +                        uint64_t last_totp_time; //@unused +                        uint8_t last_interval; //@unused +                    } __packed _unused; +                    uint8_t temporary_user_password[25]; + +                    bool isValid() const { return (slot_number & 0xF0); } +                    std::string dissect() const { +                      std::stringstream ss; +                      ss << "temporary_user_password:\t" << temporary_user_password << std::endl; +                      ss << "slot_number:\t" << (int)(slot_number) << std::endl; +                      return ss.str(); +                    } +                } __packed; + +                struct ResponsePayload { +                    union { +                        uint8_t whole_response[18]; //14 bytes reserved for config, but used only 1 +                        struct { +                            uint32_t code; +                            union{ +                                uint8_t _slot_config; +                                struct{ +                                    bool use_8_digits   : 1; +                                    bool use_enter      : 1; +                                    bool use_tokenID    : 1; +                                }; +                            }; +                        } __packed; +                    } __packed; + +                    bool isValid() const { return true; } +                    std::string dissect() const { +                      std::stringstream ss; +                      ss << "code:\t" << (code) << std::endl; +                      ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl; +                      ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl; +                      ss << "\tuse_enter(1):\t" << use_enter << std::endl; +                      ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl; +                      return ss.str(); +                    } +                } __packed; + +                typedef Transaction<command_id(), struct CommandPayload, struct ResponsePayload> +                    CommandTransaction; +            }; + + +            class GetTOTP : Command<CommandID::GET_CODE> { +                //user auth +            public: +                struct CommandPayload { +                    uint8_t slot_number; +                    uint64_t challenge; //@unused +                    uint64_t last_totp_time; //@unused +                    uint8_t last_interval; //@unused +                    uint8_t temporary_user_password[25]; + +                    bool isValid() const { return !(slot_number & 0xF0); } +                    std::string dissect() const { +                      std::stringstream ss; +                      ss << "temporary_user_password:\t" << temporary_user_password << std::endl; +                      ss << "slot_number:\t" << (int)(slot_number) << std::endl; +                      ss << "challenge:\t" << (challenge) << std::endl; +                      ss << "last_totp_time:\t" << (last_totp_time) << std::endl; +                      ss << "last_interval:\t" << (int)(last_interval) << std::endl; +                      return ss.str(); +                    } +                } __packed; + +                struct ResponsePayload { +                    union { +                        uint8_t whole_response[18]; //14 bytes reserved for config, but used only 1 +                        struct { +                            uint32_t code; +                            union{ +                                uint8_t _slot_config; +                                struct{ +                                    bool use_8_digits   : 1; +                                    bool use_enter      : 1; +                                    bool use_tokenID    : 1; +                                }; +                            }; +                        } __packed ; +                    } __packed ; + +                    bool isValid() const { return true; } +                    std::string dissect() const { +                      std::stringstream ss; +                      ss << "code:\t" << (code) << std::endl; +                      ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl; +                      ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl; +                      ss << "\tuse_enter(1):\t" << use_enter << std::endl; +                      ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl; +                      return ss.str(); +                    } +                } __packed; + +                typedef Transaction<command_id(), struct CommandPayload, struct ResponsePayload> +                    CommandTransaction; +            }; + + +            class WriteGeneralConfig : Command<CommandID::WRITE_CONFIG> { +                //admin auth +            public: +                struct CommandPayload { +                    union{ +                        uint8_t config[5]; +                        struct{ +                            uint8_t numlock;     /** 0-1: HOTP slot number from which the code will be get on double press, other value - function disabled */ +                            uint8_t capslock;    /** same as numlock */ +                            uint8_t scrolllock;  /** same as numlock */ +                            uint8_t enable_user_password; +                            uint8_t delete_user_password; +                        }; +                    }; +                    uint8_t temporary_admin_password[25]; + +                    std::string dissect() const { +                      std::stringstream ss; +                      ss << "numlock:\t" << (int)numlock << std::endl; +                      ss << "capslock:\t" << (int)capslock << std::endl; +                      ss << "scrolllock:\t" << (int)scrolllock << std::endl; +                      ss << "enable_user_password:\t" << (bool) enable_user_password << std::endl; +                      ss << "delete_user_password:\t" << (bool) delete_user_password << std::endl; +                      return ss.str(); +                    } +                } __packed; + +                typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> +                    CommandTransaction; +            }; +        } +    } +} +#endif //LIBNITROKEY_STICK10_COMMANDS_0_8_H diff --git a/include/stick20_commands.h b/include/stick20_commands.h index 1af9da3..e6df770 100644 --- a/include/stick20_commands.h +++ b/include/stick20_commands.h @@ -125,7 +125,17 @@ namespace nitrokey {                      uint16_t MagicNumber_StickConfig_u16;                      uint8_t ReadWriteFlagUncryptedVolume_u8;                      uint8_t ReadWriteFlagCryptedVolume_u8; + +                    union{                      uint8_t VersionInfo_au8[4]; +                        struct { +                            uint8_t __unused; +                            uint8_t major; +                            uint8_t __unused2; +                            uint8_t minor; +                        } __packed versionInfo; +                    }; +                      uint8_t ReadWriteFlagHiddenVolume_u8;                      uint8_t FirmwareLocked_u8;                      uint8_t NewSDCardFound_u8; @@ -13,10 +13,11 @@ std::vector<uint8_t> hex_string_to_byte(const char* hexString){      const size_t big_string_size = 256; //arbitrary 'big' number      const size_t s_size = strlen(hexString);      const size_t d_size = s_size/2; -    if (s_size%2!=0 || s_size==0 || s_size>big_string_size){ +    if (s_size%2!=0 || s_size>big_string_size){          throw InvalidHexString(0);      } -    auto data = std::vector<uint8_t>(d_size, 0); +    auto data = std::vector<uint8_t>(); +    data.reserve(d_size);      char buf[2];      for(int i=0; i<s_size; i++){ @@ -28,7 +29,7 @@ std::vector<uint8_t> hex_string_to_byte(const char* hexString){          }          buf[i%2] = c;          if (i%2==1){ -            data[i/2] = strtoul(buf, NULL, 16) & 0xFF; +            data.push_back( strtoul(buf, NULL, 16) & 0xFF );          }      }      return data; diff --git a/unittest/conftest.py b/unittest/conftest.py index 68227d5..88bf7d0 100644 --- a/unittest/conftest.py +++ b/unittest/conftest.py @@ -2,6 +2,16 @@ import pytest  from misc import ffi +device_type = None + +def skip_if_device_version_lower_than(allowed_devices): +    global device_type +    model, version = device_type +    infinite_version_number = 999 +    if allowed_devices.get(model, infinite_version_number) > version: +        pytest.skip('This device model is not applicable to run this test') + +  @pytest.fixture(scope="module")  def C(request):      fp = '../NK_C_API.h' @@ -24,7 +34,11 @@ def C(request):      nk_login = C.NK_login_auto()      if nk_login != 1:          print('No devices detected!') -    assert nk_login == 1  # returns 0 if not connected or wrong model or 1 when connected +    assert nk_login != 0  # returns 0 if not connected or wrong model or 1 when connected +    global device_type +    firmware_version = C.NK_get_major_firmware_version() +    model = 'P' if firmware_version in [7,8] else 'S' +    device_type = (model, firmware_version)      # assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK      # assert C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP) == DeviceErrorCode.STATUS_OK diff --git a/unittest/constants.py b/unittest/constants.py index 78a219b..0897b42 100644 --- a/unittest/constants.py +++ b/unittest/constants.py @@ -2,7 +2,7 @@ from enum import Enum  from misc import to_hex  RFC_SECRET_HR = '12345678901234567890' -RFC_SECRET = to_hex(RFC_SECRET_HR)  # '12345678901234567890' +RFC_SECRET = to_hex(RFC_SECRET_HR)  # '31323334353637383930...'  # print( repr((RFC_SECRET, RFC_SECRET_, len(RFC_SECRET))) ) diff --git a/unittest/misc.py b/unittest/misc.py index b45436d..f4d7731 100644 --- a/unittest/misc.py +++ b/unittest/misc.py @@ -20,21 +20,29 @@ def cast_pointer_to_tuple(obj, typen, len):      #     config = cast_pointer_to_tuple(config_raw_data, 'uint8_t', 5)      return tuple(ffi.cast("%s [%d]" % (typen, len), obj)[0:len]) -def get_firmware_version_from_status(C): -    status = gs(C.NK_status()) -    status = [s if 'firmware_version' in s else '' for s in status.split('\n')] -    firmware = status[0].split(':')[1] + +def get_devices_firmware_version(C): +    firmware = C.NK_get_major_firmware_version()      return firmware  def is_pro_rtm_07(C): -    firmware = get_firmware_version_from_status(C) -    return '07 00' in firmware +    firmware = get_devices_firmware_version(C) +    return firmware == 7 + + +def is_pro_rtm_08(C): +    firmware = get_devices_firmware_version(C) +    return firmware == 8  def is_storage(C):      """      exact firmware storage is sent by other function      """ -    firmware = get_firmware_version_from_status(C) -    return '01 00' in firmware
\ No newline at end of file +    # TODO identify connected device directly +    return not is_pro_rtm_08(C) and not is_pro_rtm_07(C) + + +def is_long_OTP_secret_handled(C): +    return is_pro_rtm_08(C) or is_storage(C) and get_devices_firmware_version(C) > 43 diff --git a/unittest/requirements.txt b/unittest/requirements.txt new file mode 100644 index 0000000..7224741 --- /dev/null +++ b/unittest/requirements.txt @@ -0,0 +1,4 @@ +cffi +pytest-repeat +pytest-randomly +enum diff --git a/unittest/test3.cc b/unittest/test3.cc new file mode 100644 index 0000000..9049365 --- /dev/null +++ b/unittest/test3.cc @@ -0,0 +1,220 @@ +// +// Created by sz on 08.11.16. +// + +#define CATCH_CONFIG_MAIN  // This tells Catch to provide a main() + +static const char *const default_admin_pin = "12345678"; +static const char *const default_user_pin = "123456"; +const char * temporary_password = "123456789012345678901234"; +const char * RFC_SECRET = "12345678901234567890"; + +#include "catch.hpp" + +#include <iostream> +#include <string.h> +#include <NitrokeyManager.h> +#include "device_proto.h" +#include "log.h" +#include "stick10_commands_0.8.h" +//#include "stick20_commands.h" + +using namespace std; +using namespace nitrokey::device; +using namespace nitrokey::proto; +using namespace nitrokey::proto::stick10_08; +using namespace nitrokey::log; +using namespace nitrokey::misc; + +void connect_and_setup(Stick10 &stick) { +  bool connected = stick.connect(); +  REQUIRE(connected == true); +  Log::instance().set_loglevel(Loglevel::DEBUG); +} + +void authorize(Stick10 &stick) { +  auto authreq = get_payload<FirstAuthenticate>(); +  strcpy((char *) (authreq.card_password), default_admin_pin); +  strcpy((char *) (authreq.temporary_password), temporary_password); +  FirstAuthenticate::CommandTransaction::run(stick, authreq); + +  auto user_auth = get_payload<UserAuthenticate>(); +  strcpyT(user_auth.temporary_password, temporary_password); +  strcpyT(user_auth.card_password, default_user_pin); +  UserAuthenticate::CommandTransaction::run(stick, user_auth); +} + +TEST_CASE("write slot", "[pronew]"){ +  Stick10 stick; +  connect_and_setup(stick); +  authorize(stick); + +  auto p2 = get_payload<SendOTPData>(); +  strcpyT(p2.temporary_admin_password, temporary_password); +  p2.setTypeName(); +  strcpyT(p2.data, "test name aaa"); +  stick10_08::SendOTPData::CommandTransaction::run(stick, p2); + +  p2 = get_payload<SendOTPData>(); +  strcpyT(p2.temporary_admin_password, temporary_password); +  strcpyT(p2.data, RFC_SECRET); +  p2.setTypeSecret(); +  stick10_08::SendOTPData::CommandTransaction::run(stick, p2); + +  auto p = get_payload<WriteToOTPSlot>(); +  strcpyT(p.temporary_admin_password, temporary_password); +  p.use_8_digits = true; +  p.slot_number = 0 + 0x10; +  p.slot_counter_or_interval = 0; +  stick10_08::WriteToOTPSlot::CommandTransaction::run(stick, p); + +  auto pc = get_payload<WriteGeneralConfig>(); +  pc.enable_user_password = 0; +  strcpyT(pc.temporary_admin_password, temporary_password); +  WriteGeneralConfig::CommandTransaction::run(stick, pc); + +  auto p3 = get_payload<GetHOTP>(); +  p3.slot_number = 0 + 0x10; +  GetHOTP::CommandTransaction::run(stick, p3); +   +} + + +TEST_CASE("erase slot", "[pronew]"){ +  Stick10 stick; +  connect_and_setup(stick); +  authorize(stick); + +  auto p = get_payload<WriteGeneralConfig>(); +  p.enable_user_password = 0; +  strcpyT(p.temporary_admin_password, temporary_password); +  WriteGeneralConfig::CommandTransaction::run(stick, p); + +  auto p3 = get_payload<GetHOTP>(); +  p3.slot_number = 0 + 0x10; +  GetHOTP::CommandTransaction::run(stick, p3); + +  auto erase_payload = get_payload<EraseSlot>(); +  erase_payload.slot_number = 0 + 0x10; +  strcpyT(erase_payload.temporary_admin_password, temporary_password); +  EraseSlot::CommandTransaction::run(stick, erase_payload); + +  auto p4 = get_payload<GetHOTP>(); +  p4.slot_number = 0 + 0x10; +  REQUIRE_THROWS( +      GetHOTP::CommandTransaction::run(stick, p4) +  ); +} + +TEST_CASE("write general config", "[pronew]") { +  Stick10 stick; +  connect_and_setup(stick); +  authorize(stick); + +  auto p = get_payload<WriteGeneralConfig>(); +  p.enable_user_password = 1; +  REQUIRE_THROWS( +      WriteGeneralConfig::CommandTransaction::run(stick, p); +  ); +  strcpyT(p.temporary_admin_password, temporary_password); +  WriteGeneralConfig::CommandTransaction::run(stick, p); +} + +TEST_CASE("authorize user HOTP", "[pronew]") { +  Stick10 stick; +  connect_and_setup(stick); +  authorize(stick); + +  { +    auto p = get_payload<WriteGeneralConfig>(); +    p.enable_user_password = 1; +    strcpyT(p.temporary_admin_password, temporary_password); +    WriteGeneralConfig::CommandTransaction::run(stick, p); +  } + +  auto p2 = get_payload<SendOTPData>(); +  strcpyT(p2.temporary_admin_password, temporary_password); +  p2.setTypeName(); +  strcpyT(p2.data, "test name aaa"); +  stick10_08::SendOTPData::CommandTransaction::run(stick, p2); + +  p2 = get_payload<SendOTPData>(); +  strcpyT(p2.temporary_admin_password, temporary_password); +  strcpyT(p2.data, RFC_SECRET); +  p2.setTypeSecret(); +  stick10_08::SendOTPData::CommandTransaction::run(stick, p2); + +  auto p = get_payload<WriteToOTPSlot>(); +  strcpyT(p.temporary_admin_password, temporary_password); +  p.use_8_digits = true; +  p.slot_number = 0 + 0x10; +  p.slot_counter_or_interval = 0; +  stick10_08::WriteToOTPSlot::CommandTransaction::run(stick, p); + + +  auto p3 = get_payload<GetHOTP>(); +  p3.slot_number = 0 + 0x10; +  REQUIRE_THROWS( +      GetHOTP::CommandTransaction::run(stick, p3); +  ); +  strcpyT(p3.temporary_user_password, temporary_password); +  auto code_response = GetHOTP::CommandTransaction::run(stick, p3); +  REQUIRE(code_response.data().code == 84755224); + +} + +TEST_CASE("check firmware version", "[pronew]") { +  Stick10 stick; +  connect_and_setup(stick); + +  auto p = GetStatus::CommandTransaction::run(stick); +  REQUIRE(p.data().firmware_version == 8); +} + +TEST_CASE("authorize user TOTP", "[pronew]") { +  Stick10 stick; +  connect_and_setup(stick); +  authorize(stick); + +  { +    auto p = get_payload<WriteGeneralConfig>(); +    p.enable_user_password = 1; +    strcpyT(p.temporary_admin_password, temporary_password); +    WriteGeneralConfig::CommandTransaction::run(stick, p); +  } + +  auto p2 = get_payload<SendOTPData>(); +  strcpyT(p2.temporary_admin_password, temporary_password); +  p2.setTypeName(); +  strcpyT(p2.data, "test name TOTP"); +  stick10_08::SendOTPData::CommandTransaction::run(stick, p2); + +  p2 = get_payload<SendOTPData>(); +  strcpyT(p2.temporary_admin_password, temporary_password); +  strcpyT(p2.data, RFC_SECRET); +  p2.setTypeSecret(); +  stick10_08::SendOTPData::CommandTransaction::run(stick, p2); + +  auto p = get_payload<WriteToOTPSlot>(); +  strcpyT(p.temporary_admin_password, temporary_password); +  p.use_8_digits = true; +  p.slot_number = 0 + 0x20; +  p.slot_counter_or_interval = 30; +  stick10_08::WriteToOTPSlot::CommandTransaction::run(stick, p); + +  auto p_get_totp = get_payload<GetTOTP>(); +  p_get_totp.slot_number = 0 + 0x20; + +  REQUIRE_THROWS( +      GetTOTP::CommandTransaction::run(stick, p_get_totp); +  ); +  strcpyT(p_get_totp.temporary_user_password, temporary_password); + +  auto p_set_time = get_payload<SetTime>(); +  p_set_time.reset = 1; +  p_set_time.time = 59; +  SetTime::CommandTransaction::run(stick, p_set_time); +  auto code = GetTOTP::CommandTransaction::run(stick, p_get_totp); +  REQUIRE(code.data().code == 94287082); + +}
\ No newline at end of file diff --git a/unittest/test_library.py b/unittest/test_library.py index d0eef80..b24c72a 100644 --- a/unittest/test_library.py +++ b/unittest/test_library.py @@ -1,8 +1,9 @@  import pytest -from misc import ffi, gs, to_hex +from misc import ffi, gs, to_hex, is_pro_rtm_07, is_long_OTP_secret_handled  from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET, LibraryErrors +  def test_too_long_strings(C):      new_password = '123123123'      long_string = 'a' * 100 @@ -35,30 +36,28 @@ def test_invalid_slot(C):      assert gs(C.NK_get_password_safe_slot_login(invalid_slot)) == ''      assert C.NK_get_last_command_status() == LibraryErrors.INVALID_SLOT +  @pytest.mark.parametrize("invalid_hex_string", -                         ['text', '00  ', '0xff', 'zzzzzzzzzzzz', 'fff', '', 'f' * 257, 'f' * 258]) +                         ['text', '00  ', '0xff', 'zzzzzzzzzzzz', 'fff', 'f' * 257, 'f' * 258])  def test_invalid_secret_hex_string_for_OTP_write(C, invalid_hex_string):      """      Tests for invalid secret hex string during writing to OTP slot. Invalid strings are not hexadecimal number,      empty or longer than 255 characters.      """ +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK      assert C.NK_write_hotp_slot(1, 'slot_name', invalid_hex_string, 0, True, False, False, '',                                  DefaultPasswords.ADMIN_TEMP) == LibraryErrors.INVALID_HEX_STRING      assert C.NK_write_totp_slot(1, 'python_test', invalid_hex_string, 30, True, False, False, "",                                  DefaultPasswords.ADMIN_TEMP) == LibraryErrors.INVALID_HEX_STRING -  def test_warning_binary_bigger_than_secret_buffer(C):      invalid_hex_string = to_hex('1234567890') * 3 +    if is_long_OTP_secret_handled(C): +        invalid_hex_string *= 2      assert C.NK_write_hotp_slot(1, 'slot_name', invalid_hex_string, 0, True, False, False, '',                                  DefaultPasswords.ADMIN_TEMP) == LibraryErrors.TARGET_BUFFER_SIZE_SMALLER_THAN_SOURCE -@pytest.mark.xfail(reason="TODO") -def test_OTP_secret_started_from_null(C): -    assert False - -  @pytest.mark.skip(reason='Experimental')  def test_clear(C):      d = 'asdasdasd' diff --git a/unittest/test_pro.py b/unittest/test_pro.py index 6ab2af9..4a2a504 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -1,10 +1,15 @@  import pytest +from conftest import skip_if_device_version_lower_than  from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET  from misc import ffi, gs, wait, cast_pointer_to_tuple -from misc import is_pro_rtm_07, is_storage +from misc import is_pro_rtm_07, is_pro_rtm_08, is_storage +  def test_enable_password_safe(C): +    """ +    All Password Safe tests depend on AES keys being initialized. They will fail otherwise. +    """      assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK      assert C.NK_enable_password_safe('wrong_password') == DeviceErrorCode.WRONG_PASSWORD      assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK @@ -61,7 +66,7 @@ def test_password_safe_slot_status(C):  def test_issue_device_locks_on_second_key_generation_in_sequence(C): -    if is_pro_rtm_07(C): +    if is_pro_rtm_07(C) or is_pro_rtm_08(C):          pytest.skip("issue to register: device locks up "                       "after below commands sequence (reinsertion fixes), skipping for now")      assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK @@ -75,6 +80,19 @@ def test_regenerate_aes_key(C):      assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK +def test_enable_password_safe_after_factory_reset(C): +    assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK +    assert C.NK_factory_reset(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK +    wait(10) +    if is_storage(C): +        assert C.NK_clear_new_sd_card_warning(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK +    enable_password_safe_result = C.NK_enable_password_safe(DefaultPasswords.USER) +    assert enable_password_safe_result == DeviceErrorCode.STATUS_AES_DEC_FAILED \ +           or is_storage(C) and enable_password_safe_result == DeviceErrorCode.WRONG_PASSWORD +    assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK +    assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK + +  @pytest.mark.xfail(reason="NK Pro firmware bug: regenerating AES key command not always results in cleared slot data")  def test_destroy_password_safe(C):      """ @@ -96,6 +114,7 @@ def test_destroy_password_safe(C):      assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK      assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK +    assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK      assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK      assert gs(C.NK_get_password_safe_slot_name(0)) != 'slotname1' @@ -242,6 +261,7 @@ def test_HOTP_token(C):          assert hotp_code != 0          assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK +  def test_HOTP_counters(C):      """      # https://tools.ietf.org/html/rfc4226#page-32 @@ -285,6 +305,7 @@ def test_HOTP_64bit_counter(C):          assert C.NK_write_hotp_slot(slot_number, 'python_test', RFC_SECRET, t, use_8_digits, False, False, "",                                      DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK          code_device = str(C.NK_get_hotp_code(slot_number)) +        code_device = '0'+code_device if len(code_device) < 6 else code_device          dev_res += (t, code_device)          lib_res += (t, lib_at(t))      assert dev_res == lib_res @@ -310,14 +331,18 @@ def test_TOTP_64bit_time(C):          assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK          assert C.NK_totp_set_time(t) == DeviceErrorCode.STATUS_OK          code_device = str((C.NK_get_totp_code(slot_number, T, 0, 30))) +        code_device = '0'+code_device if len(code_device) < 6 else code_device          dev_res += (t, code_device)          lib_res += (t, lib_at(t))      assert dev_res == lib_res -@pytest.mark.xfail(reason="NK Pro: possible firmware bug or communication issue: set time command not always changes the time on stick thus failing this test, " -                          "this does not influence normal use since setting time is not done every TOTP code request" -                          "Rarely fail occurs on NK Storage") +@pytest.mark.xfail(reason="NK Pro: Test fails in 50% of cases due to test vectors set 1 second before interval count change" +                          "Here time is changed on seconds side only and miliseconds part is not being reset apparently" +                          "This results in available time to test of half a second on average, thus 50% failed cases" +                          "With disabled two first test vectors test passess 10/10 times" +                          "Fail may also occurs on NK Storage with lower occurrency since it needs less time to execute " +                          "commands")  @pytest.mark.parametrize("PIN_protection", [False, True, ])  def test_TOTP_RFC_usepin(C, PIN_protection):      slot_number = 1 @@ -338,8 +363,8 @@ def test_TOTP_RFC_usepin(C, PIN_protection):      # Mode: Sha1, time step X=30      test_data = [          #Time         T (hex)               TOTP -        (59,          0x1,                94287082), -        (1111111109,  0x00000000023523EC, 7081804), +        (59,          0x1,                94287082), # Warning - test vector time 1 second before interval count changes +        (1111111109,  0x00000000023523EC, 7081804), # Warning - test vector time 1 second before interval count changes          (1111111111,  0x00000000023523ED, 14050471),          (1234567890,  0x000000000273EF07, 89005924),          (2000000000,  0x0000000003F940AA, 69279037), @@ -359,6 +384,7 @@ def test_TOTP_RFC_usepin(C, PIN_protection):          correct += expected_code == code_from_device      assert data == responses or correct == len(test_data) +  def test_get_slot_names(C):      C.NK_set_debug(True)      assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK @@ -455,8 +481,6 @@ def test_read_write_config(C):  def test_factory_reset(C): -    if is_storage(C): -        pytest.skip('Recovery not implemented for NK Storage')      C.NK_set_debug(True)      assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK      assert C.NK_write_config(255, 255, 255, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK @@ -473,6 +497,8 @@ def test_factory_reset(C):      assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK      assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK      assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK +    if is_storage(C): +       assert C.NK_clear_new_sd_card_warning(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK  def test_get_status(C): @@ -486,3 +512,235 @@ def test_get_serial_number(C):      sn = gs(sn)      assert len(sn) > 0      print(('Serial number of the device: ', sn)) + + +@pytest.mark.parametrize("secret", ['000001', '00'*10+'ff', '00'*19+'ff', '000102', +                                    '00'*29+'ff', '00'*39+'ff', '002EF43F51AFA97BA2B46418768123C9E1809A5B' ]) +def test_OTP_secret_started_from_null(C, secret): +    """ +    NK Pro 0.8+, NK Storage 0.43+ +    """ +    skip_if_device_version_lower_than({'S': 43, 'P': 8}) +    if len(secret) > 40: +        # feature: 320 bit long secret handling +        skip_if_device_version_lower_than({'S': 44, 'P': 8}) + +    oath = pytest.importorskip("oath") +    lib_at = lambda t: oath.hotp(secret, t, format='dec6') +    PIN_protection = False +    use_8_digits = False +    slot_number = 1 +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection, +                             DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    dev_res = [] +    lib_res = [] +    for t in range(1,5): +        assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +        assert C.NK_write_hotp_slot(slot_number, 'null_secret', secret, t, use_8_digits, False, False, "", +                                    DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +        code_device = str(C.NK_get_hotp_code(slot_number)) +        code_device = '0'+code_device if len(code_device) < 6 else code_device +        dev_res += (t, code_device) +        lib_res += (t, lib_at(t)) +    assert dev_res == lib_res + + +@pytest.mark.parametrize("counter", [0, 3, 7, 0xffff, +                                     0xffffffff, +                                     0xffffffffffffffff] ) +def test_HOTP_slots_read_write_counter(C, counter): +    """ +    Write different counters to all HOTP slots, read code and compare with 3rd party +    :param counter: +    """ +    if counter >= 1e7: +        # Storage does not handle counters longer than 7 digits +        skip_if_device_version_lower_than({'P': 7}) + +    secret = RFC_SECRET +    oath = pytest.importorskip("oath") +    lib_at = lambda t: oath.hotp(secret, t, format='dec6') +    PIN_protection = False +    use_8_digits = False +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection, +                             DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    dev_res = [] +    lib_res = [] +    for slot_number in range(3): +        assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +        assert C.NK_write_hotp_slot(slot_number, 'HOTP rw' + str(slot_number), secret, counter, use_8_digits, False, False, "", +                                    DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +        code_device = str(C.NK_get_hotp_code(slot_number)) +        code_device = '0'+code_device if len(code_device) < 6 else code_device +        dev_res += (counter, code_device) +        lib_res += (counter, lib_at(counter)) +    assert dev_res == lib_res + + +@pytest.mark.parametrize("period", [30,60] ) +@pytest.mark.parametrize("time", range(21,70,20) ) +def test_TOTP_slots_read_write_at_time_period(C, time, period): +    """ +    Write to all TOTP slots with specified period, read code at specified time +    and compare with 3rd party +    """ +    secret = RFC_SECRET +    oath = pytest.importorskip("oath") +    lib_at = lambda t: oath.totp(RFC_SECRET, t=t, period=period) +    PIN_protection = False +    use_8_digits = False +    T = 0 +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection, +                             DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    dev_res = [] +    lib_res = [] +    for slot_number in range(15): +        assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +        assert C.NK_write_totp_slot(slot_number, 'TOTP rw' + str(slot_number), secret, period, use_8_digits, False, False, "", +                                    DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +        assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +        assert C.NK_totp_set_time(time) == DeviceErrorCode.STATUS_OK +        code_device = str(C.NK_get_totp_code(slot_number, T, 0, period)) +        code_device = '0'+code_device if len(code_device) < 6 else code_device +        dev_res += (time, code_device) +        lib_res += (time, lib_at(time)) +    assert dev_res == lib_res + + +@pytest.mark.parametrize("secret", [RFC_SECRET, 2*RFC_SECRET, '12'*10, '12'*30] ) +def test_TOTP_secrets(C, secret): +    ''' +    NK Pro 0.8+, NK Storage 0.44+ +    ''' +    skip_if_device_version_lower_than({'S': 44, 'P': 8}) + +    if is_pro_rtm_07(C) and len(secret)>20*2: #*2 since secret is in hex +        pytest.skip("Secret lengths over 20 bytes are not supported by NK Pro 0.7 ") +    slot_number = 0 +    time = 0 +    period = 30 +    oath = pytest.importorskip("oath") +    lib_at = lambda t: oath.totp(secret, t=t, period=period) +    PIN_protection = False +    use_8_digits = False +    T = 0 +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection, +                             DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    dev_res = [] +    lib_res = [] +    assert C.NK_write_totp_slot(slot_number, 'secret' + str(len(secret)), secret, period, use_8_digits, False, False, "", +                                DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_totp_set_time(time) == DeviceErrorCode.STATUS_OK +    code_device = str(C.NK_get_totp_code(slot_number, T, 0, period)) +    code_device = '0'+code_device if len(code_device) < 6 else code_device +    dev_res += (time, code_device) +    lib_res += (time, lib_at(time)) +    assert dev_res == lib_res + + +@pytest.mark.parametrize("secret", [RFC_SECRET, 2*RFC_SECRET, '12'*10, '12'*30] ) +def test_HOTP_secrets(C, secret): +    """ +    NK Pro 0.8+, NK Storage 0.44+ +    feature needed: support for 320bit secrets +    """ +    skip_if_device_version_lower_than({'S': 44, 'P': 8}) + +    slot_number = 0 +    counter = 0 +    oath = pytest.importorskip("oath") +    lib_at = lambda t: oath.hotp(secret, counter=t) +    PIN_protection = False +    use_8_digits = False +    T = 0 +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection, +                             DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    dev_res = [] +    lib_res = [] +    assert C.NK_write_hotp_slot(slot_number, 'secret' + str(len(secret)), secret, counter, use_8_digits, False, False, "", +                                DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    code_device = str(C.NK_get_hotp_code(slot_number)) +    code_device = '0'+code_device if len(code_device) < 6 else code_device +    dev_res += (counter, code_device) +    lib_res += (counter, lib_at(counter)) +    assert dev_res == lib_res + + +def test_special_double_press(C): +    """ +    requires manual check after function run +    double press each of num-, scroll-, caps-lock and check inserted OTP codes (each 1st should be 755224) +    on nkpro 0.7 scrolllock should do nothing, on nkpro 0.8+ should return OTP code +    """ +    secret = RFC_SECRET +    counter = 0 +    PIN_protection = False +    use_8_digits = False +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_write_config(0, 1, 2, PIN_protection, not PIN_protection, +                             DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    for slot_number in range(3): +        assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +        assert C.NK_write_hotp_slot(slot_number, 'double' + str(slot_number), secret, counter, use_8_digits, False, False, "", +                                DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    # requires manual check + + +def test_edit_OTP_slot(C): +    """ +    should change slots counter and name without changing its secret (using null secret for second update) +    """ +    # counter does not reset under Storage v0.43 +    skip_if_device_version_lower_than({'S': 44, 'P': 7}) + +    secret = RFC_SECRET +    counter = 0 +    PIN_protection = False +    use_8_digits = False +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection, +                             DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    slot_number = 0 +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    first_name = 'edit slot' +    assert C.NK_write_hotp_slot(slot_number, first_name, secret, counter, use_8_digits, False, False, "", +                                DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert gs(C.NK_get_hotp_slot_name(slot_number)) == first_name + + +    first_code = C.NK_get_hotp_code(slot_number) +    changed_name = 'changedname' +    empty_secret = '' +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_write_hotp_slot(slot_number, changed_name, empty_secret, counter, use_8_digits, False, False, "", +                                DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    second_code = C.NK_get_hotp_code(slot_number) +    assert first_code == second_code +    assert gs(C.NK_get_hotp_slot_name(slot_number)) == changed_name + + +@pytest.mark.skip +@pytest.mark.parametrize("secret", ['31323334353637383930'*2,'31323334353637383930'*4] ) +def test_TOTP_codes_from_nitrokeyapp(secret, C): +    """ +    Helper test for manual TOTP check of written secret by Nitrokey App +    Destined to run by hand +    """ +    slot_number = 0 +    PIN_protection = False +    period = 30 +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection, +                             DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    code_device = str(C.NK_get_totp_code(slot_number, 0, 0, period)) +    code_device = '0'+code_device if len(code_device) < 6 else code_device + +    oath = pytest.importorskip("oath") +    lib_at = lambda : oath.totp(secret, period=period) +    print (lib_at()) +    assert lib_at() == code_device diff --git a/unittest/test_storage.py b/unittest/test_storage.py index 01276ce..a1c59aa 100644 --- a/unittest/test_storage.py +++ b/unittest/test_storage.py @@ -1,9 +1,10 @@ -import pytest +import pprint -from misc import ffi, gs, wait, cast_pointer_to_tuple -from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET, LibraryErrors +import pytest -import pprint +from conftest import skip_if_device_version_lower_than +from constants import DefaultPasswords, DeviceErrorCode +from misc import gs, wait  pprint = pprint.PrettyPrinter(indent=4).pprint @@ -22,6 +23,7 @@ def get_dict_from_dissect(status):  def test_get_status_storage(C): +    skip_if_device_version_lower_than({'S': 43})      status_pointer = C.NK_get_status_storage_as_string()      assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK      status_string = gs(status_pointer) @@ -32,6 +34,7 @@ def test_get_status_storage(C):  def test_sd_card_usage(C): +    skip_if_device_version_lower_than({'S': 43})      data_pointer = C.NK_get_SD_usage_data_as_string()      assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK      data_string = gs(data_pointer) @@ -41,11 +44,13 @@ def test_sd_card_usage(C):  def test_encrypted_volume_unlock(C): +    skip_if_device_version_lower_than({'S': 43})      assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK      assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK  def test_encrypted_volume_unlock_hidden(C): +    skip_if_device_version_lower_than({'S': 43})      hidden_volume_password = 'hiddenpassword'      assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK      assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK @@ -54,6 +59,7 @@ def test_encrypted_volume_unlock_hidden(C):  @pytest.mark.skip(reason='hangs device, to report')  def test_encrypted_volume_setup_multiple_hidden(C): +    skip_if_device_version_lower_than({'S': 43})      hidden_volume_password = 'hiddenpassword'      p = lambda i: hidden_volume_password + str(i)      assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK @@ -67,25 +73,30 @@ def test_encrypted_volume_setup_multiple_hidden(C):  def test_unencrypted_volume_set_read_only(C): +    skip_if_device_version_lower_than({'S': 43})      assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK      assert C.NK_set_unencrypted_read_only(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK  def test_unencrypted_volume_set_read_write(C): +    skip_if_device_version_lower_than({'S': 43})      assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK      assert C.NK_set_unencrypted_read_write(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK  def test_export_firmware(C): +    skip_if_device_version_lower_than({'S': 43})      assert C.NK_export_firmware(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK  def test_clear_new_sd_card_notification(C): +    skip_if_device_version_lower_than({'S': 43})      assert C.NK_clear_new_sd_card_warning(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK  @pytest.mark.skip  def test_fill_SD_card(C): +    skip_if_device_version_lower_than({'S': 43})      status = C.NK_fill_SD_card_with_random_data(DefaultPasswords.ADMIN)      assert status == DeviceErrorCode.STATUS_OK or status == DeviceErrorCode.BUSY      while 1: @@ -97,12 +108,14 @@ def test_fill_SD_card(C):  def test_get_busy_progress_on_idle(C): +    skip_if_device_version_lower_than({'S': 43})      value = C.NK_get_progress_bar_value()      assert value == -1      assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK  def test_change_update_password(C): +    skip_if_device_version_lower_than({'S': 43})      wrong_password = 'aaaaaaaaaaa'      assert C.NK_change_update_password(wrong_password, DefaultPasswords.UPDATE_TEMP) == DeviceErrorCode.WRONG_PASSWORD      assert C.NK_change_update_password(DefaultPasswords.UPDATE, DefaultPasswords.UPDATE_TEMP) == DeviceErrorCode.STATUS_OK @@ -110,5 +123,6 @@ def test_change_update_password(C):  def test_send_startup(C): +    skip_if_device_version_lower_than({'S': 43})      time_seconds_from_epoch = 0 # FIXME set proper date      assert C.NK_send_startup(time_seconds_from_epoch) == DeviceErrorCode.STATUS_OK | 
