diff options
| author | szszszsz <szszszsz@users.noreply.github.com> | 2016-11-26 19:51:11 +0100 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2016-11-26 19:51:11 +0100 | 
| commit | f60f2cf0144a91769a5fc00fac1314d2e00cdf0d (patch) | |
| tree | ad2a4513b1ad01b225a519ac10cafa3e583a26a1 | |
| parent | d841239bc9ece1ce969c293783219cceb001fc67 (diff) | |
| parent | cdd16f3f184b2745094da39de3f815aea6633fdb (diff) | |
| download | libnitrokey-f60f2cf0144a91769a5fc00fac1314d2e00cdf0d.tar.gz libnitrokey-f60f2cf0144a91769a5fc00fac1314d2e00cdf0d.tar.bz2 | |
Merge pull request #52 from Nitrokey/14-storage_commands
Support Nitrokey Storage
| -rw-r--r-- | .gitignore | 6 | ||||
| -rw-r--r-- | CMakeLists.txt | 5 | ||||
| -rw-r--r-- | Makefile | 6 | ||||
| -rw-r--r-- | NK_C_API.cc | 97 | ||||
| -rw-r--r-- | NK_C_API.h | 123 | ||||
| -rw-r--r-- | NitrokeyManager.cc | 106 | ||||
| -rw-r--r-- | device.cc | 4 | ||||
| -rw-r--r-- | include/LongOperationInProgressException.h | 28 | ||||
| -rw-r--r-- | include/NitrokeyManager.h | 28 | ||||
| -rw-r--r-- | include/command.h | 84 | ||||
| -rw-r--r-- | include/command_id.h | 59 | ||||
| -rw-r--r-- | include/device.h | 1 | ||||
| -rw-r--r-- | include/device_proto.h | 44 | ||||
| -rw-r--r-- | include/misc.h | 30 | ||||
| -rw-r--r-- | include/stick20_commands.h | 600 | ||||
| -rw-r--r-- | misc.cc | 21 | ||||
| -rw-r--r-- | unittest/Makefile | 2 | ||||
| -rw-r--r-- | unittest/conftest.py | 42 | ||||
| -rw-r--r-- | unittest/constants.py | 33 | ||||
| -rw-r--r-- | unittest/misc.py | 40 | ||||
| -rw-r--r-- | unittest/test2.cc | 254 | ||||
| -rw-r--r-- | unittest/test_command_ids_header.h | 41 | ||||
| -rw-r--r-- | unittest/test_library.py | 67 | ||||
| -rw-r--r-- | unittest/test_pro.py (renamed from unittest/test_bindings.py) | 172 | ||||
| -rw-r--r-- | unittest/test_storage.py | 114 | 
25 files changed, 1444 insertions, 563 deletions
| @@ -1,3 +1,9 @@  *.sw*  *.log  *.o +unittest/build/ +*.pyc +core +.cache/ +.idea/ +CMakeFiles/ diff --git a/CMakeLists.txt b/CMakeLists.txt index eedfd35..e9cd54f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,9 @@ set(SOURCE_FILES      NitrokeyManager.cc          NK_C_API.cc include/CommandFailedException.h include/LibraryException.h          unittest/test_C_API.cpp -        unittest/catch_main.cpp) +        unittest/catch_main.cpp +        unittest/test2.cc +        include/LongOperationInProgressException.h +        )  add_executable(libnitrokey ${SOURCE_FILES})
\ No newline at end of file @@ -30,14 +30,14 @@ $(BUILD)/%.o: %.cc $(DEPENDS)  clean:  	rm -f $(OBJ)  	rm -f $(BUILD)/libnitrokey.so -	make -C unittest clean +	${MAKE} -C unittest clean  mrproper: clean  	rm -f $(BUILD)/*.d -	make -C unittest mrproper +	${MAKE} -C unittest mrproper  unittest: $(BUILD)/libnitrokey.so -	make -C unittest +	${MAKE} -C unittest  	cd unittest/build && ln -fs ../../build/libnitrokey.so .  .PHONY: all clean mrproper unittest diff --git a/NK_C_API.cc b/NK_C_API.cc index 7110fca..d42840b 100644 --- a/NK_C_API.cc +++ b/NK_C_API.cc @@ -375,5 +375,102 @@ extern int NK_login_auto() {      });  } +// storage commands + +extern int NK_send_startup(uint64_t seconds_from_epoch){ +  auto m = NitrokeyManager::instance(); +  return get_without_result([&](){ +      m->send_startup(seconds_from_epoch); +  }); +} + +extern int NK_unlock_encrypted_volume(const char* user_pin){ +    auto m = NitrokeyManager::instance(); +    return get_without_result([&](){ +        m->unlock_encrypted_volume(user_pin); +    }); +} + +extern int NK_unlock_hidden_volume(const char* hidden_volume_password){ +    auto m = NitrokeyManager::instance(); +    return get_without_result([&](){ +        m->unlock_hidden_volume(hidden_volume_password); +    }); +} + +extern int NK_create_hidden_volume(uint8_t slot_nr, uint8_t start_percent, uint8_t end_percent, +                                   const char *hidden_volume_password){ +    auto m = NitrokeyManager::instance(); +    return get_without_result([&](){ +        m->create_hidden_volume( slot_nr,  start_percent,  end_percent, +        hidden_volume_password); +    }); +} + +extern int NK_set_unencrypted_read_only(const char* user_pin){ +    auto m = NitrokeyManager::instance(); +    return get_without_result([&](){ +        m->set_unencrypted_read_only(user_pin); +    }); +} + +extern int NK_set_unencrypted_read_write(const char* user_pin){ +    auto m = NitrokeyManager::instance(); +    return get_without_result([&](){ +        m->set_unencrypted_read_write(user_pin); +    }); +} + +extern int NK_export_firmware(const char* admin_pin) { +    auto m = NitrokeyManager::instance(); +    return get_without_result([&](){ +        m->export_firmware(admin_pin) ; +    }); +} + +extern int NK_clear_new_sd_card_warning(const char* admin_pin) { +    auto m = NitrokeyManager::instance(); +    return get_without_result([&](){ +        m->clear_new_sd_card_warning(admin_pin); +    }); +} + +extern int NK_fill_SD_card_with_random_data(const char* admin_pin) { +    auto m = NitrokeyManager::instance(); +    return get_without_result([&](){ +        m->fill_SD_card_with_random_data(admin_pin); +    }); +} + +extern int NK_change_update_password(const char* current_update_password, +                                     const char* new_update_password) { +    auto m = NitrokeyManager::instance(); +    return get_without_result([&](){ +        m->change_update_password(current_update_password, new_update_password); +    }); +} + +extern const char* NK_get_status_storage_as_string() { +  auto m = NitrokeyManager::instance(); +  return get_with_string_result([&](){ +      return m->get_status_storage_as_string(); +  }); +} + +extern const char* NK_get_SD_usage_data_as_string() { +  auto m = NitrokeyManager::instance(); +  return get_with_string_result([&](){ +      return m->get_SD_usage_data_as_string(); +  }); +} + +extern int NK_get_progress_bar_value() { +  auto m = NitrokeyManager::instance(); +  return get_with_result([&](){ +      return m->get_progress_bar_value(); +  }); +} + +  } @@ -324,6 +324,129 @@ extern int NK_erase_password_safe_slot(uint8_t slot_number);   */  extern int NK_is_AES_supported(const char *user_password); + + + + +/** + * This command is typically run to initiate + * communication with the device (altough not required). + * It sets time on device and returns its current status + * - a combination of set_time and get_status_storage commands + * Storage only + * @param seconds_from_epoch date and time expressed in seconds + */ +extern int NK_send_startup(uint64_t seconds_from_epoch); + +/** + * Unlock encrypted volume. + * Storage only + * @param user_pin user pin 20 characters + * @return command processing error code + */ +extern int NK_unlock_encrypted_volume(const char* user_pin); + +/** + * Unlock hidden volume and lock encrypted volume. + * Requires encrypted volume to be unlocked. + * Storage only + * @param hidden_volume_password 20 characters + * @return command processing error code + */ +extern int NK_unlock_hidden_volume(const char* hidden_volume_password); + +/** + * Create hidden volume. + * Requires encrypted volume to be unlocked. + * Storage only + * @param slot_nr slot number in range 0-3 + * @param start_percent volume begin expressed in percent of total available storage, int in range 0-99 + * @param end_percent volume end expressed in percent of total available storage, int in range 1-100 + * @param hidden_volume_password 20 characters + * @return command processing error code + */ +extern int NK_create_hidden_volume(uint8_t slot_nr, uint8_t start_percent, uint8_t end_percent, +                                   const char *hidden_volume_password); + +/** + * Make unencrypted volume read-only. + * Device hides unencrypted volume for a second therefore make sure + * buffers are flushed before running. + * Storage only + * @param user_pin 20 characters + * @return command processing error code + */ +extern int NK_set_unencrypted_read_only(const char* user_pin); + +/** + * Make unencrypted volume read-write. + * Device hides unencrypted volume for a second therefore make sure + * buffers are flushed before running. + * Storage only + * @param user_pin 20 characters + * @return command processing error code + */ +extern int NK_set_unencrypted_read_write(const char* user_pin); + +/** + * Exports device's firmware to unencrypted volume. + * Storage only + * @param admin_pin 20 characters + * @return command processing error code + */ +extern int NK_export_firmware(const char* admin_pin) ; + +/** + * Clear new SD card notification. It is set after factory reset. + * Storage only + * @param admin_pin 20 characters + * @return command processing error code + */ +extern int NK_clear_new_sd_card_warning(const char* admin_pin) ; + +/** + * Fill SD card with random data. + * Should be done on first stick initialization after creating keys. + * Storage only + * @param admin_pin 20 characters + * @return command processing error code + */ +extern int NK_fill_SD_card_with_random_data(const char* admin_pin) ; + +/** + * Change update password. + * Update password is used for entering update mode, where firmware + * could be uploaded using dfu-programmer or other means. + * Storage only + * @param current_update_password 20 characters + * @param new_update_password 20 characters + * @return command processing error code + */ +extern int NK_change_update_password(const char* current_update_password, +                                 const char* new_update_password); + +/** + * Get Storage stick status as string. + * Storage only + * @return string with devices attributes + */ +extern const char* NK_get_status_storage_as_string(); + +/** + * Get SD card usage attributes as string. + * Usable during hidden volumes creation. + * Storage only + * @return string with SD card usage attributes + */ +extern const char* NK_get_SD_usage_data_as_string(); + +/** + * Get progress value of current long operation. + * Storage only + * @return int in range 0-100 or -1 if device is not busy + */ +extern int NK_get_progress_bar_value(); +  } diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index 4c2c834..20f4f14 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -3,6 +3,7 @@  #include "include/NitrokeyManager.h"  #include "include/LibraryException.h"  #include <algorithm> +#include "include/misc.h"  namespace nitrokey{ @@ -16,7 +17,7 @@ namespace nitrokey{        nitrokey::log::Log::instance()(std::string("strcpyT sizes dest src ")                                       +std::to_string(s_dest)+ " "                                       +std::to_string(strlen(src))+ " " -          ,nitrokey::log::Loglevel::DEBUG); +          ,nitrokey::log::Loglevel::DEBUG_L2);          if (strlen(src) > s_dest){              throw TooLongStringException(strlen(src), s_dest, src);          } @@ -94,7 +95,7 @@ namespace nitrokey{      void NitrokeyManager::set_debug(bool state) {          if (state){ -            Log::instance().set_loglevel(Loglevel::DEBUG_L2); +            Log::instance().set_loglevel(Loglevel::DEBUG);          } else {              Log::instance().set_loglevel(Loglevel::ERROR);          } @@ -304,10 +305,10 @@ namespace nitrokey{              case DeviceModel::STORAGE:              {                  auto p = get_payload<ChangeAdminUserPin20Current>(); -                strcpyT(p.old_pin, current_PIN); +                strcpyT(p.password, current_PIN);                  p.set_kind(StoKind);                  auto p2 = get_payload<ChangeAdminUserPin20New>(); -                strcpyT(p2.new_pin, new_PIN); +                strcpyT(p2.password, new_PIN);                  p2.set_kind(StoKind);                  ChangeAdminUserPin20Current::CommandTransaction::run(*device, p);                  ChangeAdminUserPin20New::CommandTransaction::run(*device, p2); @@ -420,8 +421,8 @@ namespace nitrokey{              }              case DeviceModel::STORAGE : {                  auto p = get_payload<stick20::CreateNewKeys>(); -                strcpyT(p.admin_password, admin_password); -                p.setKindPrefixed(); +                strcpyT(p.password, admin_password); +                p.set_defaults();                  stick20::CreateNewKeys::CommandTransaction::run(*device, p);                  break;              } @@ -445,13 +446,13 @@ namespace nitrokey{          }          case DeviceModel::STORAGE : {            auto p2 = get_payload<ChangeAdminUserPin20Current>(); -          p2.set_kind(PasswordKind::Admin); -          strcpyT(p2.old_pin, admin_password); +          p2.set_defaults(); +          strcpyT(p2.password, 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); +          auto p3 = get_payload<stick20::UnlockUserPin>(); +          p3.set_defaults(); +          strcpyT(p3.password, new_user_password); +          stick20::UnlockUserPin::CommandTransaction::run(*device, p3);            break;          }        } @@ -486,4 +487,83 @@ namespace nitrokey{          return true;      } -} +    //storage commands + +    void NitrokeyManager::send_startup(uint64_t seconds_from_epoch){ +      auto p = get_payload<stick20::SendStartup>(); +//      p.set_defaults(); //set current time +      p.localtime = seconds_from_epoch; +      stick20::SendStartup::CommandTransaction::run(*device, p); +    } + +    void NitrokeyManager::unlock_encrypted_volume(const char* user_pin){ +      misc::execute_password_command<stick20::EnableEncryptedPartition>(*device, user_pin); +    } + +    void NitrokeyManager::unlock_hidden_volume(const char* hidden_volume_password) { +      misc::execute_password_command<stick20::EnableHiddenEncryptedPartition>(*device, hidden_volume_password); +    } + +    //TODO check is encrypted volume unlocked before execution +    //if not return library exception +    void NitrokeyManager::create_hidden_volume(uint8_t slot_nr, uint8_t start_percent, uint8_t end_percent, +                                               const char *hidden_volume_password) { +      auto p = get_payload<stick20::SetupHiddenVolume>(); +      p.SlotNr_u8 = slot_nr; +      p.StartBlockPercent_u8 = start_percent; +      p.EndBlockPercent_u8 = end_percent; +      strcpyT(p.HiddenVolumePassword_au8, hidden_volume_password); +      stick20::SetupHiddenVolume::CommandTransaction::run(*device, p); +    } + +    void NitrokeyManager::set_unencrypted_read_only(const char* user_pin) { +      misc::execute_password_command<stick20::SendSetReadonlyToUncryptedVolume>(*device, user_pin); +    } + +    void NitrokeyManager::set_unencrypted_read_write(const char* user_pin) { +      misc::execute_password_command<stick20::SendSetReadwriteToUncryptedVolume>(*device, user_pin); +    } + +    void NitrokeyManager::export_firmware(const char* admin_pin) { +      misc::execute_password_command<stick20::ExportFirmware>(*device, admin_pin); +    } + +    void NitrokeyManager::clear_new_sd_card_warning(const char* admin_pin) { +      misc::execute_password_command<stick20::SendClearNewSdCardFound>(*device, admin_pin); +    } + +    void NitrokeyManager::fill_SD_card_with_random_data(const char* admin_pin) { +      auto p = get_payload<stick20::FillSDCardWithRandomChars>(); +      p.set_defaults(); +      strcpyT(p.admin_pin, admin_pin); +      stick20::FillSDCardWithRandomChars::CommandTransaction::run(*device, p); +    } + +    void NitrokeyManager::change_update_password(const char* current_update_password, const char* new_update_password) { +      auto p = get_payload<stick20::ChangeUpdatePassword>(); +      strcpyT(p.current_update_password, current_update_password); +      strcpyT(p.new_update_password, new_update_password); +      stick20::ChangeUpdatePassword::CommandTransaction::run(*device, p); +    } + +    const char * NitrokeyManager::get_status_storage_as_string(){ +      auto p = stick20::GetDeviceStatus::CommandTransaction::run(*device); +      return strdup(p.data().dissect().c_str()); +    } + +    const char * NitrokeyManager::get_SD_usage_data_as_string(){ +      auto p = stick20::GetSDCardOccupancy::CommandTransaction::run(*device); +      return strdup(p.data().dissect().c_str()); +    } + +    int NitrokeyManager::get_progress_bar_value(){ +      try{ +        stick20::GetDeviceStatus::CommandTransaction::run(*device); +        return -1; +      } +      catch (LongOperationInProgressException &e){ +        return e.progress_bar_value; +      } +    } + +    } @@ -95,8 +95,8 @@ Stick10::Stick10() {  Stick20::Stick20() {    m_vid = 0x20a0;    m_pid = 0x4109; -  m_retry_timeout = 20ms; +  m_retry_timeout = 200ms;    m_model = DeviceModel::STORAGE; -  m_send_receive_delay = 20ms; +  m_send_receive_delay = 200ms;    m_retry_receiving_count = 40;  } diff --git a/include/LongOperationInProgressException.h b/include/LongOperationInProgressException.h new file mode 100644 index 0000000..7f182b0 --- /dev/null +++ b/include/LongOperationInProgressException.h @@ -0,0 +1,28 @@ +// +// Created by sz on 24.10.16. +// + +#ifndef LIBNITROKEY_LONGOPERATIONINPROGRESSEXCEPTION_H +#define LIBNITROKEY_LONGOPERATIONINPROGRESSEXCEPTION_H + +#include "CommandFailedException.h" + +class LongOperationInProgressException : public CommandFailedException { + +public: +    unsigned char progress_bar_value; + +    LongOperationInProgressException( +        unsigned char _command_id, uint8_t last_command_status, unsigned char _progress_bar_value) +    : CommandFailedException(_command_id, last_command_status), progress_bar_value(_progress_bar_value){ +      nitrokey::log::Log::instance()( +          std::string("LongOperationInProgressException, progress bar status: ")+ +              std::to_string(progress_bar_value), nitrokey::log::Loglevel::DEBUG); +    } +    virtual const char *what() const throw() { +      return "Device returned busy status with long operation in progress"; +    } +}; + + +#endif //LIBNITROKEY_LONGOPERATIONINPROGRESSEXCEPTION_H diff --git a/include/NitrokeyManager.h b/include/NitrokeyManager.h index 52c18d7..60fa753 100644 --- a/include/NitrokeyManager.h +++ b/include/NitrokeyManager.h @@ -82,6 +82,33 @@ namespace nitrokey {          bool is_AES_supported(const char *user_password); +        void unlock_encrypted_volume(const char *user_password); + +        void unlock_hidden_volume(const char *hidden_volume_password); + +        void set_unencrypted_read_only(const char *user_pin); + +        void set_unencrypted_read_write(const char *user_pin); + +        void export_firmware(const char *admin_pin); + +        void clear_new_sd_card_warning(const char *admin_pin); + +        void fill_SD_card_with_random_data(const char *admin_pin); + +        void change_update_password(const char *current_update_password, const char *new_update_password); + +        void create_hidden_volume(uint8_t slot_nr, uint8_t start_percent, uint8_t end_percent, +                                  const char *hidden_volume_password); + +        void send_startup(uint64_t seconds_from_epoch); + +        const char * get_status_storage_as_string(); + +        const char *get_SD_usage_data_as_string(); + +        int get_progress_bar_value(); +          ~NitrokeyManager();      private:          NitrokeyManager(); @@ -101,7 +128,6 @@ namespace nitrokey {          template <typename ProCommand, PasswordKind StoKind>          void change_PIN_general(char *current_PIN, char *new_PIN); -      };  } diff --git a/include/command.h b/include/command.h index 715902d..0a875e4 100644 --- a/include/command.h +++ b/include/command.h @@ -5,19 +5,77 @@  #include "cxx_semantics.h"  namespace nitrokey { -namespace proto { - -template <CommandID cmd_id> -class Command : semantics::non_constructible { - public: -  constexpr static CommandID command_id() { return cmd_id; } - -  template <typename T> -  static std::string dissect(const T &) { -    return std::string("Payload dissection is unavailable"); -  } -}; -} +    namespace proto { + +        template<CommandID cmd_id> +        class Command : semantics::non_constructible { +        public: +            constexpr static CommandID command_id() { return cmd_id; } + +            template<typename T> +            std::string dissect(const T &) { +              return std::string("Payload dissection is unavailable"); +            } +        }; + +#define print_to_ss(x) ( ss << " " << (#x) <<":\t" << (x) << std::endl ); +namespace stick20{ +        enum class PasswordKind : uint8_t { +            User = 'P', +            Admin = 'A', +            AdminPrefixed +        }; + +        template<CommandID cmd_id, PasswordKind Tpassword_kind = PasswordKind::User, int password_length = 20> +        class PasswordCommand : public Command<cmd_id> { +        public: +            struct CommandPayload { +                uint8_t kind; +                uint8_t password[password_length]; + +                std::string dissect() const { +                  std::stringstream ss; +                  print_to_ss( kind ); +                  print_to_ss(password); +                  return ss.str(); +                } +                void set_kind_admin() { +                  kind = (uint8_t) 'A'; +                } +                void set_kind_admin_prefixed() { +                  kind = (uint8_t) 'P'; +                } +                void set_kind_user() { +                  kind = (uint8_t) 'P'; +                } + +                void set_defaults(){ +                  set_kind(Tpassword_kind); +                } + +                void set_kind(PasswordKind password_kind){ +                  switch (password_kind){ +                    case PasswordKind::Admin: +                      set_kind_admin(); +                    break; +                    case PasswordKind::User: +                      set_kind_user(); +                    break; +                    case PasswordKind::AdminPrefixed: +                      set_kind_admin_prefixed(); +                      break; +                  } +                }; + +            } __packed; + +            typedef Transaction<Command<cmd_id>::command_id(), struct CommandPayload, struct EmptyPayload> +                CommandTransaction; + +        }; +    } +    }  } +#undef print_to_ss  #endif diff --git a/include/command_id.h b/include/command_id.h index 8148cc1..a3806f0 100644 --- a/include/command_id.h +++ b/include/command_id.h @@ -66,43 +66,40 @@ 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, -    ENABLE_CRYPTED_PARI = 0x20, -  DISABLE_CRYPTED_PARI, -  ENABLE_HIDDEN_CRYPTED_PARI, -  DISABLE_HIDDEN_CRYPTED_PARI, -  ENABLE_FIRMWARE_UPDATE, -  EXPORT_FIRMWARE_TO_FILE, -  GENERATE_NEW_KEYS, -  FILL_SD_CARD_WITH_RANDOM_CHARS, +  DISABLE_CRYPTED_PARI = 0x20 + 1, //@unused +  ENABLE_HIDDEN_CRYPTED_PARI = 0x20 + 2, +  DISABLE_HIDDEN_CRYPTED_PARI = 0x20 + 3, //@unused +  ENABLE_FIRMWARE_UPDATE = 0x20 + 4, //enables update mode +  EXPORT_FIRMWARE_TO_FILE = 0x20 + 5, +  GENERATE_NEW_KEYS = 0x20 + 6, +  FILL_SD_CARD_WITH_RANDOM_CHARS = 0x20 + 7, -  WRITE_STATUS_DATA, -  ENABLE_READONLY_UNCRYPTED_LUN, -  ENABLE_READWRITE_UNCRYPTED_LUN, +  WRITE_STATUS_DATA = 0x20 + 8, //@unused +  ENABLE_READONLY_UNCRYPTED_LUN = 0x20 + 9, +  ENABLE_READWRITE_UNCRYPTED_LUN = 0x20 + 10, -  SEND_PASSWORD_MATRIX, -  SEND_PASSWORD_MATRIX_PINDATA, -  SEND_PASSWORD_MATRIX_SETUP, +  SEND_PASSWORD_MATRIX = 0x20 + 11, //@unused +  SEND_PASSWORD_MATRIX_PINDATA = 0x20 + 12, //@unused +  SEND_PASSWORD_MATRIX_SETUP = 0x20 + 13, //@unused -  GET_DEVICE_STATUS, -  SEND_DEVICE_STATUS, +  GET_DEVICE_STATUS = 0x20 + 14, +  SEND_DEVICE_STATUS = 0x20 + 15, -  SEND_HIDDEN_VOLUME_PASSWORD, -  SEND_HIDDEN_VOLUME_SETUP, -  SEND_PASSWORD, -  SEND_NEW_PASSWORD, -  CLEAR_NEW_SD_CARD_FOUND, +  SEND_HIDDEN_VOLUME_PASSWORD = 0x20 + 16, //@unused +  SEND_HIDDEN_VOLUME_SETUP = 0x20 + 17, +  SEND_PASSWORD = 0x20 + 18, +  SEND_NEW_PASSWORD = 0x20 + 19, +  CLEAR_NEW_SD_CARD_FOUND = 0x20 + 20, -  SEND_STARTUP, -  SEND_CLEAR_STICK_KEYS_NOT_INITIATED, -  SEND_LOCK_STICK_HARDWARE, +  SEND_STARTUP = 0x20 + 21, +  SEND_CLEAR_STICK_KEYS_NOT_INITIATED = 0x20 + 22, +  SEND_LOCK_STICK_HARDWARE = 0x20 + 23, //locks firmware upgrade -  PRODUCTION_TEST, -  SEND_DEBUG_DATA, +  PRODUCTION_TEST = 0x20 + 24, +  SEND_DEBUG_DATA = 0x20 + 25, //@unused -  CHANGE_UPDATE_PIN, +  CHANGE_UPDATE_PIN = 0x20 + 26,    GET_PW_SAFE_SLOT_STATUS = 0x60,    GET_PW_SAFE_SLOT_NAME = 0x61, @@ -112,8 +109,8 @@ enum class CommandID : uint8_t {    SET_PW_SAFE_SLOT_DATA_2 = 0x65,    PW_SAFE_ERASE_SLOT = 0x66,    PW_SAFE_ENABLE = 0x67, -  PW_SAFE_INIT_KEY = 0x68, -  PW_SAFE_SEND_DATA = 0x69, +  PW_SAFE_INIT_KEY = 0x68, //@unused +  PW_SAFE_SEND_DATA = 0x69, //@unused    SD_CARD_HIGH_WATERMARK = 0x70,    DETECT_SC_AES = 0x6a,    NEW_AES_KEY = 0x6b diff --git a/include/device.h b/include/device.h index 34b7a5b..3f18921 100644 --- a/include/device.h +++ b/include/device.h @@ -21,6 +21,7 @@ class Device {  public:    Device(); +  virtual ~Device(){disconnect();}    // lack of device is not actually an error,    // so it doesn't throw diff --git a/include/device_proto.h b/include/device_proto.h index cde1d51..0953566 100644 --- a/include/device_proto.h +++ b/include/device_proto.h @@ -16,6 +16,7 @@  #include "command_id.h"  #include "dissect.h"  #include "CommandFailedException.h" +#include "LongOperationInProgressException.h"  #define STICK20_UPDATE_MODE_VID 0x03EB  #define STICK20_UPDATE_MODE_PID 0x2FF1 @@ -89,24 +90,38 @@ namespace nitrokey {   *	command_id member in incoming HIDReport structure carries the command   *	type last used.   */ +        namespace DeviceResponseConstants{ +            //magic numbers from firmware +            static constexpr auto storage_status_absolute_address = 21; +            static constexpr auto storage_data_absolute_address = storage_status_absolute_address + 5; +            static constexpr auto header_size = 8; //from _zero to last_command_status inclusive +            static constexpr auto footer_size = 4; //crc +            static constexpr auto wrapping_size = header_size + footer_size; +        } +          template<CommandID cmd_id, typename ResponsePayload>          struct DeviceResponse { +            static constexpr auto storage_status_padding_size = +                DeviceResponseConstants::storage_status_absolute_address - DeviceResponseConstants::header_size; +              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]; +                uint8_t _padding[HID_REPORT_SIZE - DeviceResponseConstants::wrapping_size];                  ResponsePayload payload;                  struct { -                    uint8_t _storage_status_padding[20 - 8 + 1]; //starts on 20th byte minus already 8 used + zero byte +                    uint8_t _storage_status_padding[storage_status_padding_size];                      uint8_t command_counter;                      uint8_t command_id;                      uint8_t device_status; //@see stick20::device_status                      uint8_t progress_bar_value;                  } __packed storage_status;              } __packed; +              uint32_t crc;              void initialize() { bzero(this, sizeof *this); } @@ -119,9 +134,7 @@ namespace nitrokey {              }              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); @@ -216,6 +229,7 @@ namespace nitrokey {                if (!outp.isValid()) throw std::runtime_error("Invalid outgoing packet"); +              bool successful_communication = false;                int receiving_retry_counter = 0;                int sending_retry_counter = dev.get_retry_sending_count();                while (sending_retry_counter-- > 0) { @@ -263,12 +277,21 @@ namespace nitrokey {                    //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; +                      resp.last_command_crc == outp.crc && resp.isValid()){ +                    successful_communication = true; +                    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);                    } +                  if (resp.device_status == static_cast<uint8_t>(stick10::device_status::busy) && +                      static_cast<stick20::device_status>(resp.storage_status.device_status) +                      == stick20::device_status::busy_progressbar){ +                    successful_communication = true; +                    break; +                  }                    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) + @@ -282,7 +305,7 @@ namespace nitrokey {                    std::this_thread::sleep_for(dev.get_retry_timeout());                    continue;                  } -                if (resp.device_status == 0 && resp.last_command_crc == outp.crc) break; +                if (successful_communication) 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); @@ -293,7 +316,7 @@ namespace nitrokey {                clear_packet(outp);                if (status <= 0) -                throw std::runtime_error( +                throw std::runtime_error( //FIXME replace with CriticalErrorException                      std::string("Device error while executing command ") +                      std::to_string(status)); @@ -302,6 +325,13 @@ namespace nitrokey {                Log::instance()(std::string("receiving_retry_counter count: ") + std::to_string(receiving_retry_counter),                                Loglevel::DEBUG); +              if (resp.device_status == static_cast<uint8_t>(stick10::device_status::busy) && +                  static_cast<stick20::device_status>(resp.storage_status.device_status) +                  == stick20::device_status::busy_progressbar){ +                throw LongOperationInProgressException( +                    resp.command_id, resp.device_status, resp.storage_status.progress_bar_value); +              } +                if (!resp.isValid()) throw std::runtime_error("Invalid incoming packet");                if (receiving_retry_counter <= 0)                  throw std::runtime_error( diff --git a/include/misc.h b/include/misc.h index 5fcd16d..5158de0 100644 --- a/include/misc.h +++ b/include/misc.h @@ -3,11 +3,32 @@  #include <stdio.h>  #include <string>  #include <vector> +#include <string.h> +#include "log.h" +#include "LibraryException.h"  namespace nitrokey {  namespace misc { -template <typename T> +    template <typename T> +    void strcpyT(T& dest, const char* src){ + +        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); +        } +        strncpy((char*) &dest, src, s_dest); +    } + + +    template <typename T>  typename T::CommandPayload get_payload(){      //Create, initialize and return by value command payload      typename T::CommandPayload st; @@ -15,6 +36,13 @@ typename T::CommandPayload get_payload(){      return st;  } +    template<typename CMDTYPE, typename Tdev> +    void execute_password_command(Tdev &stick, const char *password) { +        auto p = get_payload<CMDTYPE>(); +        p.set_defaults(); +        strcpyT(p.password, password); +        CMDTYPE::CommandTransaction::run(stick, p); +    }      std::string hexdump(const char *p, size_t size, bool print_header=true);      uint32_t stm_crc32(const uint8_t *data, size_t size); diff --git a/include/stick20_commands.h b/include/stick20_commands.h index f4e7500..1af9da3 100644 --- a/include/stick20_commands.h +++ b/include/stick20_commands.h @@ -1,5 +1,6 @@  #ifndef STICK20_COMMANDS_H  #define STICK20_COMMANDS_H +  #include "inttypes.h"  #include "command.h"  #include <string> @@ -8,336 +9,299 @@  namespace nitrokey { -namespace proto { +    namespace proto {  /*  *	STICK20 protocol command ids  *	a superset (almost) of STICK10  */ -namespace stick20 { - -    enum class PasswordKind : uint8_t { -        User = 'P', -        Admin = 'A' -    }; - -    class ChangeAdminUserPin20Current : Command<CommandID::STICK20_CMD_SEND_PASSWORD> { -  public: -      struct CommandPayload { -          uint8_t kind; -          uint8_t old_pin[20]; -          std::string dissect() const { -            std::stringstream ss; -            ss << " old_pin:\t" <<  old_pin<< 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 ChangeAdminUserPin20New : Command<CommandID::STICK20_CMD_SEND_NEW_PASSWORD> { -    public: - -        struct CommandPayload { -            uint8_t kind; -            uint8_t new_pin[20]; -            std::string dissect() const { -              std::stringstream ss; -              ss << " new_pin:\t" << new_pin<< std::endl; -              return ss.str(); -            } -            void set_kind(PasswordKind k){ -              kind = (uint8_t)k; +#define print_to_ss(x) ( ss << " " << (#x) <<":\t" << (x) << std::endl ); +        namespace stick20 { + +            class ChangeAdminUserPin20Current : +                public PasswordCommand<CommandID::SEND_PASSWORD, PasswordKind::Admin> {}; +            class ChangeAdminUserPin20New : +                public PasswordCommand<CommandID::SEND_NEW_PASSWORD, PasswordKind::Admin> {}; +            class UnlockUserPin : +                public PasswordCommand<CommandID::UNLOCK_USER_PASSWORD, PasswordKind::Admin> {}; + +            class EnableEncryptedPartition : public PasswordCommand<CommandID::ENABLE_CRYPTED_PARI> {}; +            class DisableEncryptedPartition : public PasswordCommand<CommandID::DISABLE_CRYPTED_PARI> {}; +            class EnableHiddenEncryptedPartition : public PasswordCommand<CommandID::ENABLE_HIDDEN_CRYPTED_PARI> {}; +            class DisableHiddenEncryptedPartition : public PasswordCommand<CommandID::DISABLE_CRYPTED_PARI> {}; +            class EnableFirmwareUpdate : public PasswordCommand<CommandID::ENABLE_FIRMWARE_UPDATE> {}; + +            class ChangeUpdatePassword : Command<CommandID::CHANGE_UPDATE_PIN> { +            public: +                struct CommandPayload { +                    uint8_t __gap; +                    uint8_t current_update_password[20]; +                    uint8_t __gap2; +                    uint8_t new_update_password[20]; +                    std::string dissect() const { +                      std::stringstream ss; +                      print_to_ss( current_update_password ); +                      print_to_ss( new_update_password ); +                      return ss.str(); +                    } +                }; + +                typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> +                    CommandTransaction; +            }; + +            class ExportFirmware : public PasswordCommand<CommandID::EXPORT_FIRMWARE_TO_FILE> {}; + +            class CreateNewKeys : +                public PasswordCommand<CommandID::GENERATE_NEW_KEYS, PasswordKind::AdminPrefixed, 30> {}; + + +            class FillSDCardWithRandomChars : Command<CommandID::FILL_SD_CARD_WITH_RANDOM_CHARS> { +            public: +                enum class ChosenVolumes : uint8_t { +                    all_volumes = 0, +                    encrypted_volume = 1 +                }; + +                struct CommandPayload { +                    uint8_t volume_flag; +                    uint8_t kind; +                    uint8_t admin_pin[20]; + +                    std::string dissect() const { +                      std::stringstream ss; +                      print_to_ss( (int) volume_flag ); +                      print_to_ss( kind ); +                      print_to_ss(admin_pin); +                      return ss.str(); +                    } +                    void set_kind_user() { +                      kind = (uint8_t) 'P'; +                    } +                    void set_defaults(){ +                      set_kind_user(); +                      volume_flag = static_cast<uint8_t>(ChosenVolumes::encrypted_volume); +                    } + +                } __packed; + +                typedef Transaction<Command<CommandID::FILL_SD_CARD_WITH_RANDOM_CHARS>::command_id(), +                    struct CommandPayload, struct EmptyPayload> +                    CommandTransaction; +            }; + +            namespace StorageCommandResponsePayload{ +                using namespace DeviceResponseConstants; +              static constexpr auto padding_size = +                  storage_data_absolute_address - header_size; +              struct TransmissionData{ +                  uint8_t _padding[padding_size]; + +                  uint8_t SendCounter_u8; +                  uint8_t SendDataType_u8; +                  uint8_t FollowBytesFlag_u8; +                  uint8_t SendSize_u8; + +                  std::string dissect() const { +                    std::stringstream ss; +                    ss << "_padding:" << std::endl +                       << ::nitrokey::misc::hexdump((const char *) (_padding), +                                                    sizeof _padding); +                    print_to_ss((int) SendCounter_u8); +                    print_to_ss((int) SendDataType_u8); +                    print_to_ss((int) FollowBytesFlag_u8); +                    print_to_ss((int) SendSize_u8); +                    return ss.str(); +                  } + +              } __packed;              } -        } __packed; - -        typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> -                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 { -    uint8_t password[30];  // TODO check w/ firmware -  }; - -  typedef Transaction<CommandID::ENABLE_CRYPTED_PARI, struct CommandPayload, -                      struct EmptyPayload> CommandTransaction; -}; - -class DisableEncryptedPartition : semantics::non_constructible { - public: -  typedef Transaction<CommandID::DISABLE_CRYPTED_PARI, struct EmptyPayload, -                      struct EmptyPayload> CommandTransaction; -}; - -class EnableHiddenEncryptedPartition : semantics::non_constructible { - public: -  struct CommandPayload { -    uint8_t password[30];  // TODO check w/ firmware -  }; - -  typedef Transaction<CommandID::ENABLE_HIDDEN_CRYPTED_PARI, -                      struct CommandPayload, -                      struct EmptyPayload> CommandTransaction; -}; - -class DisableHiddenEncryptedPartition : semantics::non_constructible { - public: -  typedef Transaction<CommandID::DISABLE_CRYPTED_PARI, struct EmptyPayload, -                      struct EmptyPayload> CommandTransaction; -}; - -class EnableFirmwareUpdate : semantics::non_constructible { - public: -  struct CommandPayload { -    uint8_t password[30];  // TODO check w/ firmware -  }; - -  typedef Transaction<CommandID::ENABLE_FIRMWARE_UPDATE, struct CommandPayload, -                      struct EmptyPayload> CommandTransaction; -}; - -class UpdatePassword : semantics::non_constructible { - public: -  struct CommandPayload { -    uint8_t old_password[15]; -    uint8_t new_password[15]; -  }; - -  typedef Transaction<CommandID::CHANGE_UPDATE_PIN, struct CommandPayload, -                      struct EmptyPayload> CommandTransaction; -}; - -class ExportFirmware : semantics::non_constructible { - public: -  struct CommandPayload { -    uint8_t password[30]; -  }; - -  typedef Transaction<CommandID::EXPORT_FIRMWARE_TO_FILE, struct CommandPayload, -                      struct EmptyPayload> CommandTransaction; -}; - -    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'; +            namespace DeviceConfigurationResponsePacket{ + +                struct ResponsePayload { +                    StorageCommandResponsePayload::TransmissionData transmission_data; + +                    uint16_t MagicNumber_StickConfig_u16; +                    uint8_t ReadWriteFlagUncryptedVolume_u8; +                    uint8_t ReadWriteFlagCryptedVolume_u8; +                    uint8_t VersionInfo_au8[4]; +                    uint8_t ReadWriteFlagHiddenVolume_u8; +                    uint8_t FirmwareLocked_u8; +                    uint8_t NewSDCardFound_u8; +                    uint8_t SDFillWithRandomChars_u8; +                    uint32_t ActiveSD_CardID_u32; +                    uint8_t VolumeActiceFlag_u8; +                    uint8_t NewSmartCardFound_u8; +                    uint8_t UserPwRetryCount; +                    uint8_t AdminPwRetryCount; +                    uint32_t ActiveSmartCardID_u32; +                    uint8_t StickKeysNotInitiated; + +                    bool isValid() const { return true; } + +                    std::string dissect() const { +                      std::stringstream ss; + +                      print_to_ss(transmission_data.dissect()); +                      print_to_ss( MagicNumber_StickConfig_u16 ); +                      print_to_ss((int) ReadWriteFlagUncryptedVolume_u8 ); +                      print_to_ss((int) ReadWriteFlagCryptedVolume_u8 ); +                      print_to_ss((int) VersionInfo_au8[1] ); +                      print_to_ss((int) VersionInfo_au8[3] ); +                      print_to_ss((int) ReadWriteFlagHiddenVolume_u8 ); +                      print_to_ss((int) FirmwareLocked_u8 ); +                      print_to_ss((int) NewSDCardFound_u8 ); +                      print_to_ss((int) SDFillWithRandomChars_u8 ); +                      print_to_ss( ActiveSD_CardID_u32 ); +                      print_to_ss((int) VolumeActiceFlag_u8 ); +                      print_to_ss((int) NewSmartCardFound_u8 ); +                      print_to_ss((int) UserPwRetryCount ); +                      print_to_ss((int) AdminPwRetryCount ); +                      print_to_ss( ActiveSmartCardID_u32 ); +                      print_to_ss((int) StickKeysNotInitiated ); + +                      return ss.str(); +                    } +                } __packed;              } -        } __packed; - -        typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> -            CommandTransaction; -    }; - - -class FillSDCardWithRandomChars : semantics::non_constructible { - public: -  struct CommandPayload { -    uint8_t volume_flag; -    uint8_t password[30]; -  }; - -  typedef Transaction<CommandID::FILL_SD_CARD_WITH_RANDOM_CHARS, -                      struct CommandPayload, -                      struct EmptyPayload> CommandTransaction; -}; - -class SetupHiddenVolume : semantics::non_constructible { - public: -  typedef Transaction<CommandID::SEND_HIDDEN_VOLUME_SETUP, struct EmptyPayload, -                      struct EmptyPayload> CommandTransaction; -}; - -class SendPasswordMatrix : semantics::non_constructible { - public: -  typedef Transaction<CommandID::SEND_PASSWORD_MATRIX, struct EmptyPayload, -                      struct EmptyPayload> CommandTransaction; -}; - -class SendPasswordMatrixPinData : semantics::non_constructible { - public: -  struct CommandPayload { -    uint8_t pin_data[30];  // TODO how long actually can it be? -  }; - -  typedef Transaction<CommandID::SEND_PASSWORD_MATRIX_PINDATA, -                      struct CommandPayload, -                      struct EmptyPayload> CommandTransaction; -}; - -class SendPasswordMatrixSetup : semantics::non_constructible { - public: -  struct CommandPayload { -    uint8_t setup_data[30];  // TODO how long actually can it be? -  }; - -  typedef Transaction<CommandID::SEND_PASSWORD_MATRIX_SETUP, -                      struct CommandPayload, -                      struct EmptyPayload> CommandTransaction; -}; - -#define d(x) ss << " "#x":\t" << (int)x << std::endl; - -    class GetDeviceStatus : Command<CommandID::GET_DEVICE_STATUS> { -    public: -        static const int OUTPUT_CMD_RESULT_STICK20_STATUS_START = 20 +1; -        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]; //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; -            uint8_t status; -            uint8_t progress_bar_value; -            bool isValid() const { return true; } - -            std::string dissect() const { -              std::stringstream ss; -                d(command_counter); -                d(last_command); -                d(status); -                d(progress_bar_value); -              ss << "_padding:\t" -                 << ::nitrokey::misc::hexdump((const char *)(_padding), -                                              sizeof _padding); -              return ss.str(); -            } -        } __packed; - -        typedef Transaction<command_id(), struct EmptyPayload, struct ResponsePayload> -                CommandTransaction; -    }; - - -class SendPassword : semantics::non_constructible { - public: -  struct CommandPayload { -    uint8_t password[30]; -  }; -  typedef Transaction<CommandID::SEND_PASSWORD, struct CommandPayload, -                      struct EmptyPayload> CommandTransaction; -}; +            class SendStartup : Command<CommandID::SEND_STARTUP> { +            public: +                struct CommandPayload { +                    uint64_t localtime;  // POSIX seconds from epoch start, supports until year 2106 +                    std::string dissect() const { +                      std::stringstream ss; +                      print_to_ss( localtime ); +                      return ss.str(); +                    } +                    void set_defaults(){ +                      localtime = +                          std::chrono::duration_cast<std::chrono::seconds> ( +                              std::chrono::system_clock::now().time_since_epoch()).count(); +                    } +                }__packed; + +                using ResponsePayload = DeviceConfigurationResponsePacket::ResponsePayload; + +                typedef Transaction<command_id(), struct CommandPayload, ResponsePayload> +                    CommandTransaction; +            }; -class SendNewPassword : semantics::non_constructible { - public: -  struct CommandPayload { -    uint8_t password[30]; -  }; - -  typedef Transaction<CommandID::SEND_NEW_PASSWORD, struct CommandPayload, -                      struct EmptyPayload> CommandTransaction; -};  // TODO fix original nomenclature -class SendSetReadonlyToUncryptedVolume : semantics::non_constructible { - public: -  struct CommandPayload { -    uint8_t password[30]; -  }; - -  typedef Transaction<CommandID::ENABLE_READWRITE_UNCRYPTED_LUN, -                      struct CommandPayload, -                      struct EmptyPayload> CommandTransaction; -}; - -class SendSetReadwriteToUncryptedVolume : semantics::non_constructible { - public: -  struct CommandPayload { -    uint8_t password[30]; -  }; - -  typedef Transaction<CommandID::ENABLE_READWRITE_UNCRYPTED_LUN, -                      struct CommandPayload, -                      struct EmptyPayload> CommandTransaction; -}; - -class SendClearNewSdCardFound : semantics::non_constructible { - public: -  struct CommandPayload { -    uint8_t password[30]; -  }; - -  typedef Transaction<CommandID::CLEAR_NEW_SD_CARD_FOUND, struct CommandPayload, -                      struct EmptyPayload> CommandTransaction; -}; - -class SendStartup : semantics::non_constructible { - public: -  struct CommandPayload { -    uint64_t localtime;  // POSIX -  }; - -  typedef Transaction<CommandID::SEND_STARTUP, struct CommandPayload, -                      struct EmptyPayload> CommandTransaction; -}; - -class SendHiddenVolumeSetup : semantics::non_constructible { - public: -  struct CommandPayload { -    // TODO HiddenVolumeSetup_tst type -  }; - -  typedef Transaction<CommandID::SEND_HIDDEN_VOLUME_SETUP, -                      struct CommandPayload, -                      struct EmptyPayload> CommandTransaction; -}; - -class LockFirmware : semantics::non_constructible { - public: -  struct CommandPayload { -    uint8_t password[30]; -  }; - -  typedef Transaction<CommandID::SEND_LOCK_STICK_HARDWARE, -                      struct CommandPayload, -                      struct EmptyPayload> CommandTransaction; -}; - -class ProductionTest : semantics::non_constructible { - public: -  typedef Transaction<CommandID::PRODUCTION_TEST, struct EmptyPayload, -                      struct EmptyPayload> CommandTransaction; -}; -} -} +            class SendSetReadonlyToUncryptedVolume : public PasswordCommand<CommandID::ENABLE_READONLY_UNCRYPTED_LUN> {}; +            class SendSetReadwriteToUncryptedVolume : public PasswordCommand<CommandID::ENABLE_READWRITE_UNCRYPTED_LUN> {}; +            class SendClearNewSdCardFound : public PasswordCommand<CommandID::CLEAR_NEW_SD_CARD_FOUND> {}; + +            class GetDeviceStatus : Command<CommandID::GET_DEVICE_STATUS> { +            public: +                using ResponsePayload = DeviceConfigurationResponsePacket::ResponsePayload; + +                typedef Transaction<command_id(), struct EmptyPayload, ResponsePayload> +                    CommandTransaction; +            }; + +            class GetSDCardOccupancy : Command<CommandID::SD_CARD_HIGH_WATERMARK> { +            public: +                struct ResponsePayload { +                    uint8_t WriteLevelMin; +                    uint8_t WriteLevelMax; +                    uint8_t ReadLevelMin; +                    uint8_t ReadLevelMax; +                    std::string dissect() const { +                      std::stringstream ss; +                      print_to_ss((int) WriteLevelMin); +                      print_to_ss((int) WriteLevelMax); +                      print_to_ss((int) ReadLevelMin); +                      print_to_ss((int) ReadLevelMax); +                      return ss.str(); +                    } +                } __packed; + +                typedef Transaction<command_id(), struct EmptyPayload, struct ResponsePayload> +                    CommandTransaction; +            }; + + +            class SetupHiddenVolume : Command<CommandID::SEND_HIDDEN_VOLUME_SETUP> { +            public: +                constexpr static int MAX_HIDDEN_VOLUME_PASSWORD_SIZE = 20; +                struct CommandPayload { +                    uint8_t SlotNr_u8; +                    uint8_t StartBlockPercent_u8; +                    uint8_t EndBlockPercent_u8; +                    uint8_t HiddenVolumePassword_au8[MAX_HIDDEN_VOLUME_PASSWORD_SIZE]; +                    std::string dissect() const { +                      std::stringstream ss; +                      print_to_ss((int) SlotNr_u8); +                      print_to_ss((int) StartBlockPercent_u8); +                      print_to_ss((int) EndBlockPercent_u8); +                      print_to_ss(HiddenVolumePassword_au8); +                      return ss.str(); +                    } +                } __packed; + +                typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload> +                    CommandTransaction; +            }; + + +//disable this command for now +//            class LockFirmware : public PasswordCommand<CommandID::SEND_LOCK_STICK_HARDWARE> {}; + +            class ProductionTest : Command<CommandID::PRODUCTION_TEST> { +            public: +                struct ResponsePayload { + +                    StorageCommandResponsePayload::TransmissionData transmission_data; + +                    uint8_t FirmwareVersion_au8[2];        // 2 byte // 2 +                    uint8_t FirmwareVersionInternal_u8;    // 1 byte // 3 +                    uint8_t SD_Card_Size_u8;               // 1 byte // 4 +                    uint32_t CPU_CardID_u32;                     // 4 byte // 8 +                    uint32_t SmartCardID_u32;                    // 4 byte // 12 +                    uint32_t SD_CardID_u32;                      // 4 byte // 16 +                    uint8_t SC_UserPwRetryCount;           // User PIN retry count 1 byte  // 17 +                    uint8_t SC_AdminPwRetryCount;          // Admin PIN retry count 1 byte // 18 +                    uint8_t SD_Card_ManufacturingYear_u8;  // 1 byte // 19 +                    uint8_t SD_Card_ManufacturingMonth_u8; // 1 byte // 20 +                    uint16_t SD_Card_OEM_u16;              // 2 byte // 22 +                    uint16_t SD_WriteSpeed_u16;            // in kbyte / sec 2 byte // 24 +                    uint8_t SD_Card_Manufacturer_u8;       // 1 byte // 25 + +                    bool isValid() const { return true; } + +                    std::string dissect() const { +                      std::stringstream ss; + +                      print_to_ss(transmission_data.dissect()); +                      print_to_ss((int) FirmwareVersion_au8[0]); +                      print_to_ss((int) FirmwareVersion_au8[1]); +                      print_to_ss((int) FirmwareVersionInternal_u8); +                      print_to_ss((int) SD_Card_Size_u8); +                      print_to_ss( CPU_CardID_u32); +                      print_to_ss( SmartCardID_u32); +                      print_to_ss( SD_CardID_u32); +                      print_to_ss((int) SC_UserPwRetryCount); +                      print_to_ss((int) SC_AdminPwRetryCount); +                      print_to_ss((int) SD_Card_ManufacturingYear_u8); +                      print_to_ss((int) SD_Card_ManufacturingMonth_u8); +                      print_to_ss( SD_Card_OEM_u16); +                      print_to_ss( SD_WriteSpeed_u16); +                      print_to_ss((int) SD_Card_Manufacturer_u8); +                      return ss.str(); +                    } + +                  } __packed; + +                typedef Transaction<command_id(), struct EmptyPayload, struct ResponsePayload> +                    CommandTransaction; +            }; +        } +    }  } +#undef print_to_ss +  #endif @@ -34,6 +34,7 @@ std::vector<uint8_t> hex_string_to_byte(const char* hexString){      return data;  }; +#include <cctype>  std::string hexdump(const char *p, size_t size, bool print_header) {    std::stringstream out;    char formatbuf[128]; @@ -45,9 +46,23 @@ std::string hexdump(const char *p, size_t size, bool print_header) {            out << formatbuf;        } -    for (const char *le = p + 16; p < le && p < pend; p++) { -      snprintf(formatbuf, 128, "%02x ", uint8_t(*p)); -      out << formatbuf; +    const char* pp = p; +    for (const char *le = p + 16; p < le; p++) { +      if (p < pend){ +        snprintf(formatbuf, 128, "%02x ", uint8_t(*p)); +        out << formatbuf; +      } else { +        out << "-- "; +      } + +    } +    out << "\t"; + +    for (const char *le = pp + 16; pp < le && pp < pend; pp++) { +      if (std::isgraph(*pp)) +        out << uint8_t(*pp); +      else +        out << '.';      }      out << std::endl;    } diff --git a/unittest/Makefile b/unittest/Makefile index 9e8fbc1..dbd003e 100644 --- a/unittest/Makefile +++ b/unittest/Makefile @@ -8,7 +8,7 @@ LIB = -L../build  LDLIBS = -lnitrokey  BUILD = build -CXXFLAGS = -std=c++14 -fPIC +CXXFLAGS = -std=c++14 -fPIC -Wno-gnu-variable-sized-type-not-at-end  CXXSOURCES = $(wildcard *.cc)  TARGETS = $(CXXSOURCES:%.cc=$(BUILD)/%) diff --git a/unittest/conftest.py b/unittest/conftest.py new file mode 100644 index 0000000..68227d5 --- /dev/null +++ b/unittest/conftest.py @@ -0,0 +1,42 @@ +import pytest + +from misc import ffi + +@pytest.fixture(scope="module") +def C(request): +    fp = '../NK_C_API.h' + +    declarations = [] +    with open(fp, 'r') as f: +        declarations = f.readlines() + +    a = iter(declarations) +    for declaration in a: +        if declaration.startswith('extern') and not '"C"' in declaration: +            declaration = declaration.replace('extern', '').strip() +            while not ';' in declaration: +                declaration += (next(a)).strip() +            print(declaration) +            ffi.cdef(declaration, override=True) + +    C = ffi.dlopen("../build/libnitrokey.so") +    C.NK_set_debug(False) +    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 C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    # assert C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP) == DeviceErrorCode.STATUS_OK + +    # C.NK_status() + +    def fin(): +        print('\nFinishing connection to device') +        C.NK_logout() +        print('Finished') + +    request.addfinalizer(fin) +    C.NK_set_debug(True) + +    return C diff --git a/unittest/constants.py b/unittest/constants.py new file mode 100644 index 0000000..78a219b --- /dev/null +++ b/unittest/constants.py @@ -0,0 +1,33 @@ +from enum import Enum +from misc import to_hex + +RFC_SECRET_HR = '12345678901234567890' +RFC_SECRET = to_hex(RFC_SECRET_HR)  # '12345678901234567890' + + +# print( repr((RFC_SECRET, RFC_SECRET_, len(RFC_SECRET))) ) + +class DefaultPasswords(Enum): +    ADMIN = '12345678' +    USER = '123456' +    ADMIN_TEMP = '123123123' +    USER_TEMP = '234234234' +    UPDATE = '12345678' +    UPDATE_TEMP = '123update123' + + +class DeviceErrorCode(Enum): +    STATUS_OK = 0 +    BUSY = 1 # busy or busy progressbar in place of wrong_CRC status +    NOT_PROGRAMMED = 3 +    WRONG_PASSWORD = 4 +    STATUS_NOT_AUTHORIZED = 5 +    STATUS_AES_DEC_FAILED = 0xa + + +class LibraryErrors(Enum): +    TOO_LONG_STRING = 200 +    INVALID_SLOT = 201 +    INVALID_HEX_STRING = 202 +    TARGET_BUFFER_SIZE_SMALLER_THAN_SOURCE = 203 + diff --git a/unittest/misc.py b/unittest/misc.py new file mode 100644 index 0000000..b45436d --- /dev/null +++ b/unittest/misc.py @@ -0,0 +1,40 @@ +import cffi + +ffi = cffi.FFI() +gs = ffi.string + + +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) + + +def cast_pointer_to_tuple(obj, typen, len): +    # usage: +    #     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] +    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
\ No newline at end of file diff --git a/unittest/test2.cc b/unittest/test2.cc new file mode 100644 index 0000000..00e70e3 --- /dev/null +++ b/unittest/test2.cc @@ -0,0 +1,254 @@ +#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"; + +#include "catch.hpp" + +#include <iostream> +#include <string.h> +#include <NitrokeyManager.h> +#include "device_proto.h" +#include "log.h" +//#include "stick10_commands.h" +#include "stick20_commands.h" + +using namespace std; +using namespace nitrokey::device; +using namespace nitrokey::proto; +using namespace nitrokey::proto::stick20; +using namespace nitrokey::log; +using namespace nitrokey::misc; + + +template<typename CMDTYPE> +void execute_password_command(Device &stick, const char *password, const char kind = 'P') { +  auto p = get_payload<CMDTYPE>(); +  if (kind == 'P'){ +    p.set_kind_user(); +  } else { +    p.set_kind_admin(); +  } +  strcpyT(p.password, password); +  CMDTYPE::CommandTransaction::run(stick, p); +  this_thread::sleep_for(1000ms); +} + +/** + *  fail on purpose (will result in failed test) + *  disable from running unwillingly + */ +void SKIP_TEST() { +  CAPTURE("Failing current test to SKIP it"); +  REQUIRE(false); +} + + +TEST_CASE("long operation test", "[test_long]") { +  SKIP_TEST(); + +  Stick20 stick; +  bool connected = stick.connect(); +  REQUIRE(connected == true); +  Log::instance().set_loglevel(Loglevel::DEBUG); +  try{ +    auto p = get_payload<FillSDCardWithRandomChars>(); +    p.set_defaults(); +    strcpyT(p.admin_pin, default_admin_pin); +    FillSDCardWithRandomChars::CommandTransaction::run(stick, p); +    this_thread::sleep_for(1000ms); + +    CHECK(false); +  } +  catch (LongOperationInProgressException &progressException){ +    CHECK(true); +  } + + +  for (int i = 0; i < 30; ++i) { +    try { +      stick10::GetStatus::CommandTransaction::run(stick); +    } +    catch (LongOperationInProgressException &progressException){ +      CHECK((int)progressException.progress_bar_value>=0); +      CAPTURE((int)progressException.progress_bar_value); +      this_thread::sleep_for(2000ms); +    } + +  } + +} + + +#include "test_command_ids_header.h" + +TEST_CASE("test device commands ids", "[fast]") { + +//  REQUIRE(STICK20_CMD_START_VALUE == static_cast<uint8_t>(CommandID::START_VALUE)); +  REQUIRE(STICK20_CMD_ENABLE_CRYPTED_PARI == static_cast<uint8_t>(CommandID::ENABLE_CRYPTED_PARI)); +  REQUIRE(STICK20_CMD_DISABLE_CRYPTED_PARI == static_cast<uint8_t>(CommandID::DISABLE_CRYPTED_PARI)); +  REQUIRE(STICK20_CMD_ENABLE_HIDDEN_CRYPTED_PARI == static_cast<uint8_t>(CommandID::ENABLE_HIDDEN_CRYPTED_PARI)); +  REQUIRE(STICK20_CMD_DISABLE_HIDDEN_CRYPTED_PARI == static_cast<uint8_t>(CommandID::DISABLE_HIDDEN_CRYPTED_PARI)); +  REQUIRE(STICK20_CMD_ENABLE_FIRMWARE_UPDATE == static_cast<uint8_t>(CommandID::ENABLE_FIRMWARE_UPDATE)); +  REQUIRE(STICK20_CMD_EXPORT_FIRMWARE_TO_FILE == static_cast<uint8_t>(CommandID::EXPORT_FIRMWARE_TO_FILE)); +  REQUIRE(STICK20_CMD_GENERATE_NEW_KEYS == static_cast<uint8_t>(CommandID::GENERATE_NEW_KEYS)); +  REQUIRE(STICK20_CMD_FILL_SD_CARD_WITH_RANDOM_CHARS == static_cast<uint8_t>(CommandID::FILL_SD_CARD_WITH_RANDOM_CHARS)); + +  REQUIRE(STICK20_CMD_WRITE_STATUS_DATA == static_cast<uint8_t>(CommandID::WRITE_STATUS_DATA)); +  REQUIRE(STICK20_CMD_ENABLE_READONLY_UNCRYPTED_LUN == static_cast<uint8_t>(CommandID::ENABLE_READONLY_UNCRYPTED_LUN)); +  REQUIRE(STICK20_CMD_ENABLE_READWRITE_UNCRYPTED_LUN == static_cast<uint8_t>(CommandID::ENABLE_READWRITE_UNCRYPTED_LUN)); + +  REQUIRE(STICK20_CMD_SEND_PASSWORD_MATRIX == static_cast<uint8_t>(CommandID::SEND_PASSWORD_MATRIX)); +  REQUIRE(STICK20_CMD_SEND_PASSWORD_MATRIX_PINDATA == static_cast<uint8_t>(CommandID::SEND_PASSWORD_MATRIX_PINDATA)); +  REQUIRE(STICK20_CMD_SEND_PASSWORD_MATRIX_SETUP == static_cast<uint8_t>(CommandID::SEND_PASSWORD_MATRIX_SETUP)); + +  REQUIRE(STICK20_CMD_GET_DEVICE_STATUS == static_cast<uint8_t>(CommandID::GET_DEVICE_STATUS)); +  REQUIRE(STICK20_CMD_SEND_DEVICE_STATUS == static_cast<uint8_t>(CommandID::SEND_DEVICE_STATUS)); + +  REQUIRE(STICK20_CMD_SEND_HIDDEN_VOLUME_PASSWORD == static_cast<uint8_t>(CommandID::SEND_HIDDEN_VOLUME_PASSWORD)); +  REQUIRE(STICK20_CMD_SEND_HIDDEN_VOLUME_SETUP == static_cast<uint8_t>(CommandID::SEND_HIDDEN_VOLUME_SETUP)); +  REQUIRE(STICK20_CMD_SEND_PASSWORD == static_cast<uint8_t>(CommandID::SEND_PASSWORD)); +  REQUIRE(STICK20_CMD_SEND_NEW_PASSWORD == static_cast<uint8_t>(CommandID::SEND_NEW_PASSWORD)); +  REQUIRE(STICK20_CMD_CLEAR_NEW_SD_CARD_FOUND == static_cast<uint8_t>(CommandID::CLEAR_NEW_SD_CARD_FOUND)); + +  REQUIRE(STICK20_CMD_SEND_STARTUP == static_cast<uint8_t>(CommandID::SEND_STARTUP)); +  REQUIRE(STICK20_CMD_SEND_CLEAR_STICK_KEYS_NOT_INITIATED == static_cast<uint8_t>(CommandID::SEND_CLEAR_STICK_KEYS_NOT_INITIATED)); +  REQUIRE(STICK20_CMD_SEND_LOCK_STICK_HARDWARE == static_cast<uint8_t>(CommandID::SEND_LOCK_STICK_HARDWARE)); + +  REQUIRE(STICK20_CMD_PRODUCTION_TEST == static_cast<uint8_t>(CommandID::PRODUCTION_TEST)); +  REQUIRE(STICK20_CMD_SEND_DEBUG_DATA == static_cast<uint8_t>(CommandID::SEND_DEBUG_DATA)); + +  REQUIRE(STICK20_CMD_CHANGE_UPDATE_PIN == static_cast<uint8_t>(CommandID::CHANGE_UPDATE_PIN)); + +} + +TEST_CASE("test device internal status with various commands", "[fast]") { +  Stick20 stick; +  bool connected = stick.connect(); +  REQUIRE(connected == true); + +  Log::instance().set_loglevel(Loglevel::DEBUG); +  auto p = get_payload<stick20::SendStartup>(); +  p.set_defaults(); +  auto device_status = stick20::SendStartup::CommandTransaction::run(stick, p); +  REQUIRE(device_status.data().AdminPwRetryCount == 3); +  REQUIRE(device_status.data().UserPwRetryCount == 3); +  REQUIRE(device_status.data().ActiveSmartCardID_u32 != 0); + +  auto production_status = stick20::ProductionTest::CommandTransaction::run(stick); +  REQUIRE(production_status.data().SD_Card_Size_u8 == 8); +  REQUIRE(production_status.data().SD_CardID_u32 != 0); + +  auto sdcard_occupancy = stick20::GetSDCardOccupancy::CommandTransaction::run(stick); +  REQUIRE((int) sdcard_occupancy.data().ReadLevelMin >= 0); +  REQUIRE((int) sdcard_occupancy.data().ReadLevelMax <= 100); +  REQUIRE((int) sdcard_occupancy.data().WriteLevelMin >= 0); +  REQUIRE((int) sdcard_occupancy.data().WriteLevelMax <= 100); +} + +TEST_CASE("setup hidden volume test", "[hidden]") { +  Stick20 stick; +  bool connected = stick.connect(); +  REQUIRE(connected == true); +  Log::instance().set_loglevel(Loglevel::DEBUG); +  stick10::LockDevice::CommandTransaction::run(stick); +  this_thread::sleep_for(2000ms); + +  auto user_pin = default_user_pin; +  execute_password_command<EnableEncryptedPartition>(stick, user_pin); + +  auto p = get_payload<stick20::SetupHiddenVolume>(); +  p.SlotNr_u8 = 0; +  p.StartBlockPercent_u8 = 70; +  p.EndBlockPercent_u8 = 90; +  auto hidden_volume_password = "123123123"; +  strcpyT(p.HiddenVolumePassword_au8, hidden_volume_password); +  stick20::SetupHiddenVolume::CommandTransaction::run(stick, p); +  this_thread::sleep_for(2000ms); + +  execute_password_command<EnableHiddenEncryptedPartition>(stick, hidden_volume_password); +} + +TEST_CASE("setup multiple hidden volumes", "[hidden]") { +  Stick20 stick; +  bool connected = stick.connect(); +  REQUIRE(connected == true); +  Log::instance().set_loglevel(Loglevel::DEBUG); + +  auto user_pin = default_user_pin; +  stick10::LockDevice::CommandTransaction::run(stick); +  this_thread::sleep_for(2000ms); +  execute_password_command<EnableEncryptedPartition>(stick, user_pin); + +  constexpr int volume_count = 4; +  for (int i = 0; i < volume_count; ++i) { +    auto p = get_payload<stick20::SetupHiddenVolume>(); +    p.SlotNr_u8 = i; +    p.StartBlockPercent_u8 = 20 + 10*i; +    p.EndBlockPercent_u8 = p.StartBlockPercent_u8+i+1; +    auto hidden_volume_password = std::string("123123123")+std::to_string(i); +    strcpyT(p.HiddenVolumePassword_au8, hidden_volume_password.c_str()); +    stick20::SetupHiddenVolume::CommandTransaction::run(stick, p); +    this_thread::sleep_for(2000ms); +  } + + +  for (int i = 0; i < volume_count; ++i) { +    execute_password_command<EnableEncryptedPartition>(stick, user_pin); +    auto hidden_volume_password = std::string("123123123")+std::to_string(i); +    execute_password_command<EnableHiddenEncryptedPartition>(stick, hidden_volume_password.c_str()); +    this_thread::sleep_for(2000ms); +  } +} + + +//in case of a bug this could change update PIN to some unexpected value +// - please save log with packet dump if this test will not pass +TEST_CASE("update password change", "[dangerous]") { +  SKIP_TEST(); + +  Stick20 stick; +  bool connected = stick.connect(); +  REQUIRE(connected == true); +  Log::instance().set_loglevel(Loglevel::DEBUG); + +  auto pass1 = default_admin_pin; +  auto pass2 = "12345678901234567890"; + +  auto data = { +      make_pair(pass1, pass2), +      make_pair(pass2, pass1), +  }; +  for (auto &&  password: data) { +    auto p = get_payload<stick20::ChangeUpdatePassword>(); +    strcpyT(p.current_update_password, password.first); +    strcpyT(p.new_update_password, password.second); +    stick20::ChangeUpdatePassword::CommandTransaction::run(stick, p); +  } +} + +TEST_CASE("general test", "[test]") { +  Stick20 stick; +  bool connected = stick.connect(); +  REQUIRE(connected == true); + +  Log::instance().set_loglevel(Loglevel::DEBUG); + +  stick10::LockDevice::CommandTransaction::run(stick); +//  execute_password_command<EnableEncryptedPartition>(stick, "123456"); +//  execute_password_command<DisableEncryptedPartition>(stick, "123456"); +//  execute_password_command<DisableHiddenEncryptedPartition>(stick, "123123123"); + +  execute_password_command<SendSetReadonlyToUncryptedVolume>(stick, default_user_pin); +  execute_password_command<SendSetReadwriteToUncryptedVolume>(stick, default_user_pin); +  execute_password_command<SendClearNewSdCardFound>(stick, default_admin_pin, 'A'); +  stick20::GetDeviceStatus::CommandTransaction::run(stick); +  this_thread::sleep_for(1000ms); +//  execute_password_command<LockFirmware>(stick, "123123123"); //CAUTION +//  execute_password_command<EnableFirmwareUpdate>(stick, "123123123"); //CAUTION FIRMWARE PIN + +  execute_password_command<ExportFirmware>(stick, "12345678", 'A'); +//  execute_password_command<FillSDCardWithRandomChars>(stick, "12345678", 'A'); + +  stick10::LockDevice::CommandTransaction::run(stick); +} diff --git a/unittest/test_command_ids_header.h b/unittest/test_command_ids_header.h new file mode 100644 index 0000000..cd55c8a --- /dev/null +++ b/unittest/test_command_ids_header.h @@ -0,0 +1,41 @@ +#ifndef LIBNITROKEY_TEST_COMMAND_IDS_HEADER_H_H +#define LIBNITROKEY_TEST_COMMAND_IDS_HEADER_H_H + +#define STICK20_CMD_START_VALUE 0x20 +#define STICK20_CMD_ENABLE_CRYPTED_PARI (STICK20_CMD_START_VALUE + 0) +#define STICK20_CMD_DISABLE_CRYPTED_PARI (STICK20_CMD_START_VALUE + 1) +#define STICK20_CMD_ENABLE_HIDDEN_CRYPTED_PARI (STICK20_CMD_START_VALUE + 2) +#define STICK20_CMD_DISABLE_HIDDEN_CRYPTED_PARI (STICK20_CMD_START_VALUE + 3) +#define STICK20_CMD_ENABLE_FIRMWARE_UPDATE (STICK20_CMD_START_VALUE + 4) +#define STICK20_CMD_EXPORT_FIRMWARE_TO_FILE (STICK20_CMD_START_VALUE + 5) +#define STICK20_CMD_GENERATE_NEW_KEYS (STICK20_CMD_START_VALUE + 6) +#define STICK20_CMD_FILL_SD_CARD_WITH_RANDOM_CHARS (STICK20_CMD_START_VALUE + 7) + +#define STICK20_CMD_WRITE_STATUS_DATA (STICK20_CMD_START_VALUE + 8) +#define STICK20_CMD_ENABLE_READONLY_UNCRYPTED_LUN (STICK20_CMD_START_VALUE + 9) +#define STICK20_CMD_ENABLE_READWRITE_UNCRYPTED_LUN (STICK20_CMD_START_VALUE + 10) + +#define STICK20_CMD_SEND_PASSWORD_MATRIX (STICK20_CMD_START_VALUE + 11) +#define STICK20_CMD_SEND_PASSWORD_MATRIX_PINDATA (STICK20_CMD_START_VALUE + 12) +#define STICK20_CMD_SEND_PASSWORD_MATRIX_SETUP (STICK20_CMD_START_VALUE + 13) + +#define STICK20_CMD_GET_DEVICE_STATUS (STICK20_CMD_START_VALUE + 14) +#define STICK20_CMD_SEND_DEVICE_STATUS (STICK20_CMD_START_VALUE + 15) + +#define STICK20_CMD_SEND_HIDDEN_VOLUME_PASSWORD (STICK20_CMD_START_VALUE + 16) +#define STICK20_CMD_SEND_HIDDEN_VOLUME_SETUP (STICK20_CMD_START_VALUE + 17) +#define STICK20_CMD_SEND_PASSWORD (STICK20_CMD_START_VALUE + 18) +#define STICK20_CMD_SEND_NEW_PASSWORD (STICK20_CMD_START_VALUE + 19) +#define STICK20_CMD_CLEAR_NEW_SD_CARD_FOUND (STICK20_CMD_START_VALUE + 20) + +#define STICK20_CMD_SEND_STARTUP (STICK20_CMD_START_VALUE + 21) +#define STICK20_CMD_SEND_CLEAR_STICK_KEYS_NOT_INITIATED (STICK20_CMD_START_VALUE + 22) +#define STICK20_CMD_SEND_LOCK_STICK_HARDWARE (STICK20_CMD_START_VALUE + 23) + +#define STICK20_CMD_PRODUCTION_TEST (STICK20_CMD_START_VALUE + 24) +#define STICK20_CMD_SEND_DEBUG_DATA (STICK20_CMD_START_VALUE + 25) + +#define STICK20_CMD_CHANGE_UPDATE_PIN (STICK20_CMD_START_VALUE + 26) + + +#endif //LIBNITROKEY_TEST_COMMAND_IDS_HEADER_H_H diff --git a/unittest/test_library.py b/unittest/test_library.py new file mode 100644 index 0000000..d0eef80 --- /dev/null +++ b/unittest/test_library.py @@ -0,0 +1,67 @@ +import pytest + +from misc import ffi, gs, to_hex +from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET, LibraryErrors + +def test_too_long_strings(C): +    new_password = '123123123' +    long_string = 'a' * 100 +    assert C.NK_change_user_PIN(long_string, new_password) == LibraryErrors.TOO_LONG_STRING +    assert C.NK_change_user_PIN(new_password, long_string) == LibraryErrors.TOO_LONG_STRING +    assert C.NK_change_admin_PIN(long_string, new_password) == LibraryErrors.TOO_LONG_STRING +    assert C.NK_change_admin_PIN(new_password, long_string) == LibraryErrors.TOO_LONG_STRING +    assert C.NK_first_authenticate(long_string, DefaultPasswords.ADMIN_TEMP) == LibraryErrors.TOO_LONG_STRING +    assert C.NK_erase_totp_slot(0, long_string) == LibraryErrors.TOO_LONG_STRING +    digits = False +    assert C.NK_write_hotp_slot(1, long_string, RFC_SECRET, 0, digits, False, False, "", +                                DefaultPasswords.ADMIN_TEMP) == LibraryErrors.TOO_LONG_STRING +    assert C.NK_write_hotp_slot(1, 'long_test', RFC_SECRET, 0, digits, False, False, "", +                                long_string) == LibraryErrors.TOO_LONG_STRING +    assert C.NK_get_hotp_code_PIN(0, long_string) == 0 +    assert C.NK_get_last_command_status() == LibraryErrors.TOO_LONG_STRING + + +def test_invalid_slot(C): +    invalid_slot = 255 +    assert C.NK_erase_totp_slot(invalid_slot, 'some password') == LibraryErrors.INVALID_SLOT +    assert C.NK_write_hotp_slot(invalid_slot, 'long_test', RFC_SECRET, 0, False, False, False, "", +                                'aaa') == LibraryErrors.INVALID_SLOT +    assert C.NK_get_hotp_code_PIN(invalid_slot, 'some password') == 0 +    assert C.NK_get_last_command_status() == LibraryErrors.INVALID_SLOT +    assert C.NK_erase_password_safe_slot(invalid_slot) == LibraryErrors.INVALID_SLOT +    assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK +    assert gs(C.NK_get_password_safe_slot_name(invalid_slot)) == '' +    assert C.NK_get_last_command_status() == LibraryErrors.INVALID_SLOT +    assert gs(C.NK_get_password_safe_slot_login(invalid_slot)) == '' +    assert C.NK_get_last_command_status() == LibraryErrors.INVALID_SLOT + +@pytest.mark.parametrize("invalid_hex_string", +                         ['text', '00  ', '0xff', 'zzzzzzzzzzzz', 'fff', '', 'f' * 257, 'f' * 258]) +def test_invalid_secret_hex_string_for_OTP_write(C, invalid_hex_string): +    """ +    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_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 +    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' +    print(d) +    C.clear_password(d) +    print(d)
\ No newline at end of file diff --git a/unittest/test_bindings.py b/unittest/test_pro.py index f7ade46..6ab2af9 100644 --- a/unittest/test_bindings.py +++ b/unittest/test_pro.py @@ -1,109 +1,8 @@  import pytest -import cffi -from enum import Enum - -ffi = cffi.FFI() -gs = ffi.string - - -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' - - -# print( repr((RFC_SECRET, RFC_SECRET_, len(RFC_SECRET))) ) - -class DefaultPasswords(Enum): -    ADMIN = '12345678' -    USER = '123456' -    ADMIN_TEMP = '123123123' -    USER_TEMP = '234234234' - - -class DeviceErrorCode(Enum): -    STATUS_OK = 0 -    NOT_PROGRAMMED = 3 -    WRONG_PASSWORD = 4 -    STATUS_NOT_AUTHORIZED = 5 -    STATUS_AES_DEC_FAILED = 0xa - - -class LibraryErrors(Enum): -    TOO_LONG_STRING = 200 -    INVALID_SLOT = 201 -    INVALID_HEX_STRING = 202 -    TARGET_BUFFER_SIZE_SMALLER_THAN_SOURCE = 203 - - -@pytest.fixture(scope="module") -def C(request): -    fp = '../NK_C_API.h' - -    declarations = [] -    with open(fp, 'r') as f: -        declarations = f.readlines() - -    a = iter(declarations) -    for declaration in a: -        if declaration.startswith('extern') and not '"C"' in declaration: -            declaration = declaration.replace('extern', '').strip() -            while not ';' in declaration: -                declaration += (next(a)).strip() -            print(declaration) -            ffi.cdef(declaration) - -    C = ffi.dlopen("../build/libnitrokey.so") -    C.NK_set_debug(False) -    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 C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK -    # assert C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP) == DeviceErrorCode.STATUS_OK - -    # C.NK_status() - -    def fin(): -        print('\nFinishing connection to device') -        C.NK_logout() -        print('Finished') - -    request.addfinalizer(fin) -    C.NK_set_debug(True) - -    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 +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  def test_enable_password_safe(C):      assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK @@ -232,39 +131,6 @@ def test_user_PIN_change(C):      assert C.NK_change_user_PIN(new_password, DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK -def test_too_long_strings(C): -    new_password = '123123123' -    long_string = 'a' * 100 -    assert C.NK_change_user_PIN(long_string, new_password) == LibraryErrors.TOO_LONG_STRING -    assert C.NK_change_user_PIN(new_password, long_string) == LibraryErrors.TOO_LONG_STRING -    assert C.NK_change_admin_PIN(long_string, new_password) == LibraryErrors.TOO_LONG_STRING -    assert C.NK_change_admin_PIN(new_password, long_string) == LibraryErrors.TOO_LONG_STRING -    assert C.NK_first_authenticate(long_string, DefaultPasswords.ADMIN_TEMP) == LibraryErrors.TOO_LONG_STRING -    assert C.NK_erase_totp_slot(0, long_string) == LibraryErrors.TOO_LONG_STRING -    digits = False -    assert C.NK_write_hotp_slot(1, long_string, RFC_SECRET, 0, digits, False, False, "", -                                DefaultPasswords.ADMIN_TEMP) == LibraryErrors.TOO_LONG_STRING -    assert C.NK_write_hotp_slot(1, 'long_test', RFC_SECRET, 0, digits, False, False, "", -                                long_string) == LibraryErrors.TOO_LONG_STRING -    assert C.NK_get_hotp_code_PIN(0, long_string) == 0 -    assert C.NK_get_last_command_status() == LibraryErrors.TOO_LONG_STRING - - -def test_invalid_slot(C): -    invalid_slot = 255 -    assert C.NK_erase_totp_slot(invalid_slot, 'some password') == LibraryErrors.INVALID_SLOT -    assert C.NK_write_hotp_slot(invalid_slot, 'long_test', RFC_SECRET, 0, False, False, False, "", -                                'aaa') == LibraryErrors.INVALID_SLOT -    assert C.NK_get_hotp_code_PIN(invalid_slot, 'some password') == 0 -    assert C.NK_get_last_command_status() == LibraryErrors.INVALID_SLOT -    assert C.NK_erase_password_safe_slot(invalid_slot) == LibraryErrors.INVALID_SLOT -    assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK -    assert gs(C.NK_get_password_safe_slot_name(invalid_slot)) == '' -    assert C.NK_get_last_command_status() == LibraryErrors.INVALID_SLOT -    assert gs(C.NK_get_password_safe_slot_login(invalid_slot)) == '' -    assert C.NK_get_last_command_status() == LibraryErrors.INVALID_SLOT - -  def test_admin_retry_counts(C):      default_admin_retry_count = 3      assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK @@ -609,14 +475,6 @@ def test_factory_reset(C):      assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK -@pytest.mark.skip(reason='Experimental') -def test_clear(C): -    d = 'asdasdasd' -    print(d) -    C.clear_password(d) -    print(d) - -  def test_get_status(C):      status = C.NK_status()      s = gs(status) @@ -628,27 +486,3 @@ def test_get_serial_number(C):      sn = gs(sn)      assert len(sn) > 0      print(('Serial number of the device: ', sn)) - - -@pytest.mark.parametrize("invalid_hex_string", -                         ['text', '00  ', '0xff', 'zzzzzzzzzzzz', 'fff', '', 'f' * 257, 'f' * 258]) -def test_invalid_secret_hex_string_for_OTP_write(C, invalid_hex_string): -    """ -    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_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 -    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 diff --git a/unittest/test_storage.py b/unittest/test_storage.py new file mode 100644 index 0000000..01276ce --- /dev/null +++ b/unittest/test_storage.py @@ -0,0 +1,114 @@ +import pytest + +from misc import ffi, gs, wait, cast_pointer_to_tuple +from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET, LibraryErrors + +import pprint +pprint = pprint.PrettyPrinter(indent=4).pprint + + +def get_dict_from_dissect(status): +    x = [] +    for s in status.split('\n'): +        try: +            if not ':' in s: continue +            ss = s.replace('\t', '').replace(' (int) ', '').split(':') +            if not len(ss) == 2: continue +            x.append(ss) +        except: +            pass +    d = {k.strip(): v.strip() for k, v in x} +    return d + + +def test_get_status_storage(C): +    status_pointer = C.NK_get_status_storage_as_string() +    assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK +    status_string = gs(status_pointer) +    assert len(status_string) > 0 +    status_dict = get_dict_from_dissect(status_string) +    default_admin_password_retry_count = 3 +    assert int(status_dict['AdminPwRetryCount']) == default_admin_password_retry_count + + +def test_sd_card_usage(C): +    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) +    assert len(data_string) > 0 +    data_dict = get_dict_from_dissect(data_string) +    assert int(data_dict['WriteLevelMax']) <= 100 + + +def test_encrypted_volume_unlock(C): +    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): +    hidden_volume_password = 'hiddenpassword' +    assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK +    assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK +    assert C.NK_create_hidden_volume(0, 20, 21, hidden_volume_password) == DeviceErrorCode.STATUS_OK +    assert C.NK_unlock_hidden_volume(hidden_volume_password) == DeviceErrorCode.STATUS_OK + +@pytest.mark.skip(reason='hangs device, to report') +def test_encrypted_volume_setup_multiple_hidden(C): +    hidden_volume_password = 'hiddenpassword' +    p = lambda i: hidden_volume_password + str(i) +    assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK +    assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK +    for i in range(4): +        assert C.NK_create_hidden_volume(i, 20+i*10, 20+i*10+i+1, p(i) ) == DeviceErrorCode.STATUS_OK +    for i in range(4): +        assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK +        assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK +        assert C.NK_unlock_hidden_volume(p(i)) == DeviceErrorCode.STATUS_OK + + +def test_unencrypted_volume_set_read_only(C): +    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): +    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): +    assert C.NK_export_firmware(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK + + +def test_clear_new_sd_card_notification(C): +    assert C.NK_clear_new_sd_card_warning(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK + + +@pytest.mark.skip +def test_fill_SD_card(C): +    status = C.NK_fill_SD_card_with_random_data(DefaultPasswords.ADMIN) +    assert status == DeviceErrorCode.STATUS_OK or status == DeviceErrorCode.BUSY +    while 1: +        value = C.NK_get_progress_bar_value() +        if value == -1: break +        assert 0 <= value <= 100 +        assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK +        wait(5) + + +def test_get_busy_progress_on_idle(C): +    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): +    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 +    assert C.NK_change_update_password(DefaultPasswords.UPDATE_TEMP, DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_OK + + +def test_send_startup(C): +    time_seconds_from_epoch = 0 # FIXME set proper date +    assert C.NK_send_startup(time_seconds_from_epoch) == DeviceErrorCode.STATUS_OK | 
