diff options
-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 |