diff --git a/app_api_table_i.h b/app_api_table_i.h index b1c29ed7105..3051f0a5ca9 100644 --- a/app_api_table_i.h +++ b/app_api_table_i.h @@ -62,4 +62,7 @@ static constexpr auto app_api_table = sort(create_array_t( bool, (TokenInfo*, const char*, size_t, PlainTokenSecretEncoding, const CryptoSettings*)), API_METHOD(totp_crypto_check_key_slot, bool, (uint8_t)), - API_METHOD(totp_bt_type_code_worker_free, void, (TotpBtTypeCodeWorkerContext*)))); \ No newline at end of file + API_METHOD(totp_bt_type_code_worker_free, void, (TotpBtTypeCodeWorkerContext*)), + API_METHOD(token_info_set_token_type_from_str, bool, (TokenInfo*, const FuriString*)), + API_METHOD(token_info_set_token_counter_from_str, bool, (TokenInfo*, const FuriString*)), + API_METHOD(token_info_get_type_as_cstr, const char*, (const TokenInfo*)))); diff --git a/application.fam b/application.fam index 46600ed72d0..b061ccf16bd 100644 --- a/application.fam +++ b/application.fam @@ -7,9 +7,9 @@ App( requires=["gui", "cli", "dialogs", "storage", "input", "notification", "bt"], stack_size=2 * 1024, order=20, - fap_version="5.00", + fap_version="5.50", fap_author="Alexander Kopachov (@akopachov)", - fap_description="Software-based TOTP authenticator for Flipper Zero device", + fap_description="Software-based TOTP/HOTP authenticator for Flipper Zero device", fap_weburl="https://github.com/akopachov/flipper-zero_authenticator", fap_category="Tools", fap_icon_assets="images", diff --git a/assets/cli/cli_help.txt b/assets/cli/cli_help.txt index 2cd941b234a..0a6467ae410 100644 --- a/assets/cli/cli_help.txt +++ b/assets/cli/cli_help.txt @@ -3,8 +3,8 @@ Usage: totp version totp (list | ls) totp (lsattr | cat) - totp (add | mk | new) [-a ] [-e ] [-d ] [-l ] [-u] [-b ]... - totp (update) [-a ] [-e ] [-n ] [-d ] [-l ] [-u] [-s] [-b ]... + totp (add | mk | new) [-t ] [-i ] [-a ] [-e ] [-d ] [-l ] [-u] [-b ]... + totp (update) [-t ] [-i ] [-a ] [-e ] [-n ] [-d ] [-l ] [-u] [-s] [-b ]... totp (delete | rm) [-f] totp (move | mv) totp pin (set | remove) [-c ] @@ -37,10 +37,12 @@ Arguments: automation Automation method to be set. Must be one of: none, usb, bt Options: + -t Token type. Must be one of: totp, hotp [default: totp] + -i Token initial counter. Applicable for HOTP tokens only. Must be positive integer number [default: 0] -a Token hashing algorithm. Must be one of: sha1, sha256, sha512, steam [default: sha1] -d Number of digits to generate, one of: 5, 6, 8 [default: 6] -e Token secret encoding, one of base32, base64 [default: base32] - -l Token lifetime duration in seconds, between: 15 and 255 [default: 30] + -l Token lifetime duration in seconds. Applicable for TOTP tokens only.Must be between: 15 and 255 [default: 30] -u Show console user input as-is without masking -b Token automation features to be enabled. Must be one of: none, enter, tab [default: none] # none - No features diff --git a/cli/plugins/details/details.c b/cli/plugins/details/details.c index 7ba2814ed3e..08f7ba220f6 100644 --- a/cli/plugins/details/details.c +++ b/cli/plugins/details/details.c @@ -19,6 +19,7 @@ typedef void (*TOTP_CLI_DETAILS_AUTOMATION_FEATURE_ITEM_FORMATTER)( typedef void (*TOTP_CLI_DETAILS_CSTR_FORMATTER)(const char* key, const char* value); typedef void (*TOTP_CLI_DETAILS_UINT8T_FORMATTER)(const char* key, uint8_t value); typedef void (*TOTP_CLI_DETAILS_SIZET_FORMATTER)(const char* key, size_t value); +typedef void (*TOTP_CLI_DETAILS_UINT64T_FORMATTER)(const char* key, uint64_t value); typedef struct { const TOTP_CLI_DETAILS_HEADER_FORMATTER header_formatter; @@ -27,6 +28,7 @@ typedef struct { const TOTP_CLI_DETAILS_CSTR_FORMATTER cstr_formatter; const TOTP_CLI_DETAILS_UINT8T_FORMATTER uint8t_formatter; const TOTP_CLI_DETAILS_SIZET_FORMATTER sizet_formatter; + const TOTP_CLI_DETAILS_UINT64T_FORMATTER uint64t_formatter; } TotpCliDetailsFormatter; static const TotpCliDetailsFormatter available_formatters[] = { @@ -35,14 +37,16 @@ static const TotpCliDetailsFormatter available_formatters[] = { .automation_feature_item_formatter = &details_output_formatter_print_automation_feature_table, .cstr_formatter = &details_output_formatter_print_cstr_table, .uint8t_formatter = &details_output_formatter_print_uint8t_table, - .sizet_formatter = &details_output_formatter_print_sizet_table}, + .sizet_formatter = &details_output_formatter_print_sizet_table, + .uint64t_formatter = &details_output_formatter_print_uint64t_table}, {.header_formatter = &details_output_formatter_print_header_tsv, .footer_formatter = &details_output_formatter_print_footer_tsv, .automation_feature_item_formatter = &details_output_formatter_print_automation_feature_tsv, .cstr_formatter = &details_output_formatter_print_cstr_tsv, .uint8t_formatter = &details_output_formatter_print_uint8t_tsv, - .sizet_formatter = &details_output_formatter_print_sizet_tsv}, + .sizet_formatter = &details_output_formatter_print_sizet_tsv, + .uint64t_formatter = &details_output_formatter_print_uint64t_tsv}, }; static void print_automation_features( @@ -103,10 +107,15 @@ static void handle(PluginState* plugin_state, FuriString* args, Cli* cli) { (*formatter->header_formatter)(); (*formatter->sizet_formatter)("Index", token_number); + (*formatter->cstr_formatter)("Type", token_info_get_type_as_cstr(token_info)); (*formatter->cstr_formatter)("Name", furi_string_get_cstr(token_info->name)); (*formatter->cstr_formatter)("Hashing algorithm", token_info_get_algo_as_cstr(token_info)); (*formatter->uint8t_formatter)("Number of digits", token_info->digits); - (*formatter->uint8t_formatter)("Token lifetime", token_info->duration); + if(token_info->type == TokenTypeTOTP) { + (*formatter->uint8t_formatter)("Token lifetime", token_info->duration); + } else if(token_info->type == TokenTypeHOTP) { + (*formatter->uint64t_formatter)("Token counter", token_info->counter); + } print_automation_features(token_info, formatter); (*formatter->footer_formatter)(); } else { @@ -128,4 +137,4 @@ static const FlipperAppPluginDescriptor plugin_descriptor = { const FlipperAppPluginDescriptor* totp_cli_details_plugin_ep() { return &plugin_descriptor; -} \ No newline at end of file +} diff --git a/cli/plugins/details/formatters/table/details_output_formatter_table.c b/cli/plugins/details/formatters/table/details_output_formatter_table.c index d5f832139d0..cd12a4bb50d 100644 --- a/cli/plugins/details/formatters/table/details_output_formatter_table.c +++ b/cli/plugins/details/formatters/table/details_output_formatter_table.c @@ -3,31 +3,35 @@ #include "../../../../cli_helpers.h" void details_output_formatter_print_header_table() { - TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n"); - TOTP_CLI_PRINTF("| %-20s | %-28s |\r\n", "Property", "Value"); - TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n"); + TOTP_CLI_PRINTF("+----------------------+-------------------------------+\r\n"); + TOTP_CLI_PRINTF("| %-20s | %-29s |\r\n", "Property", "Value"); + TOTP_CLI_PRINTF("+----------------------+-------------------------------+\r\n"); } void details_output_formatter_print_footer_table() { - TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n"); + TOTP_CLI_PRINTF("+----------------------+-------------------------------+\r\n"); } void details_output_formatter_print_automation_feature_table( const char* key, const char* feature, bool* header_printed) { - TOTP_CLI_PRINTF("| %-20s | %-28.28s |\r\n", *header_printed ? "" : key, feature); + TOTP_CLI_PRINTF("| %-20s | %-29.29s |\r\n", *header_printed ? "" : key, feature); *header_printed = true; } void details_output_formatter_print_cstr_table(const char* key, const char* value) { - TOTP_CLI_PRINTF("| %-20s | %-28.28s |\r\n", key, value); + TOTP_CLI_PRINTF("| %-20s | %-29.29s |\r\n", key, value); } void details_output_formatter_print_uint8t_table(const char* key, uint8_t value) { - TOTP_CLI_PRINTF("| %-20s | %-28" PRIu8 " |\r\n", key, value); + TOTP_CLI_PRINTF("| %-20s | %-29" PRIu8 " |\r\n", key, value); } void details_output_formatter_print_sizet_table(const char* key, size_t value) { - TOTP_CLI_PRINTF("| %-20s | %-28" PRIu16 " |\r\n", key, value); + TOTP_CLI_PRINTF("| %-20s | %-29" PRIu16 " |\r\n", key, value); +} + +void details_output_formatter_print_uint64t_table(const char* key, uint64_t value) { + TOTP_CLI_PRINTF("| %-20s | %-29" PRIu64 " |\r\n", key, value); } diff --git a/cli/plugins/details/formatters/table/details_output_formatter_table.h b/cli/plugins/details/formatters/table/details_output_formatter_table.h index bdb6bf115bf..d948cc096bf 100644 --- a/cli/plugins/details/formatters/table/details_output_formatter_table.h +++ b/cli/plugins/details/formatters/table/details_output_formatter_table.h @@ -13,3 +13,4 @@ void details_output_formatter_print_automation_feature_table( void details_output_formatter_print_cstr_table(const char* key, const char* value); void details_output_formatter_print_uint8t_table(const char* key, uint8_t value); void details_output_formatter_print_sizet_table(const char* key, size_t value); +void details_output_formatter_print_uint64t_table(const char* key, uint64_t value); diff --git a/cli/plugins/details/formatters/tsv/details_output_formatter_tsv.c b/cli/plugins/details/formatters/tsv/details_output_formatter_tsv.c index 7f553552b9e..4357e9a346a 100644 --- a/cli/plugins/details/formatters/tsv/details_output_formatter_tsv.c +++ b/cli/plugins/details/formatters/tsv/details_output_formatter_tsv.c @@ -28,3 +28,7 @@ void details_output_formatter_print_uint8t_tsv(const char* key, uint8_t value) { void details_output_formatter_print_sizet_tsv(const char* key, size_t value) { TOTP_CLI_PRINTF("%s\t%" PRIu16 "\r\n", key, value); } + +void details_output_formatter_print_uint64t_tsv(const char* key, uint64_t value) { + TOTP_CLI_PRINTF("%s\t%" PRIu64 "\r\n", key, value); +} diff --git a/cli/plugins/details/formatters/tsv/details_output_formatter_tsv.h b/cli/plugins/details/formatters/tsv/details_output_formatter_tsv.h index 15b20ffcb34..0e61b80ae14 100644 --- a/cli/plugins/details/formatters/tsv/details_output_formatter_tsv.h +++ b/cli/plugins/details/formatters/tsv/details_output_formatter_tsv.h @@ -13,3 +13,4 @@ void details_output_formatter_print_automation_feature_tsv( void details_output_formatter_print_cstr_tsv(const char* key, const char* value); void details_output_formatter_print_uint8t_tsv(const char* key, uint8_t value); void details_output_formatter_print_sizet_tsv(const char* key, size_t value); +void details_output_formatter_print_uint64t_tsv(const char* key, uint64_t value); diff --git a/cli/plugins/list/formatters/table/list_output_formatter_table.c b/cli/plugins/list/formatters/table/list_output_formatter_table.c index a72f770a351..32124021a68 100644 --- a/cli/plugins/list/formatters/table/list_output_formatter_table.c +++ b/cli/plugins/list/formatters/table/list_output_formatter_table.c @@ -3,21 +3,21 @@ #include "../../../../cli_helpers.h" void list_output_formatter_print_header_table() { - TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n"); - TOTP_CLI_PRINTF("| %-3s | %-25s | %-6s | %-s | %-s |\r\n", "#", "Name", "Algo", "Ln", "Dur"); - TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n"); + TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+------+\r\n"); + TOTP_CLI_PRINTF("| %-3s | %-25s | %-6s | %-s | %-s |\r\n", "#", "Name", "Algo", "Ln", "Type"); + TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+------+\r\n"); } void list_output_formatter_print_body_item_table(size_t index, const TokenInfo* token_info) { TOTP_CLI_PRINTF( - "| %-3" PRIu16 " | %-25.25s | %-6s | %-2" PRIu8 " | %-3" PRIu8 " |\r\n", + "| %-3" PRIu16 " | %-25.25s | %-6s | %-2" PRIu8 " | %-4s |\r\n", index + 1, furi_string_get_cstr(token_info->name), token_info_get_algo_as_cstr(token_info), token_info->digits, - token_info->duration); + token_info_get_type_as_cstr(token_info)); } void list_output_formatter_print_footer_table() { - TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n"); -} \ No newline at end of file + TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+------+\r\n"); +} diff --git a/cli/plugins/list/formatters/tsv/list_output_formatter_tsv.c b/cli/plugins/list/formatters/tsv/list_output_formatter_tsv.c index 374231f20df..564bb36134c 100644 --- a/cli/plugins/list/formatters/tsv/list_output_formatter_tsv.c +++ b/cli/plugins/list/formatters/tsv/list_output_formatter_tsv.c @@ -3,18 +3,18 @@ #include "../../../../cli_helpers.h" void list_output_formatter_print_header_tsv() { - TOTP_CLI_PRINTF("%s\t%s\t%s\t%s\t%s\r\n", "#", "Name", "Algo", "Ln", "Dur"); + TOTP_CLI_PRINTF("%s\t%s\t%s\t%s\t%s\r\n", "#", "Name", "Algo", "Ln", "Type"); } void list_output_formatter_print_body_item_tsv(size_t index, const TokenInfo* token_info) { TOTP_CLI_PRINTF( - "%" PRIu16 "\t%s\t%s\t%" PRIu8 "\t%" PRIu8 "\r\n", + "%" PRIu16 "\t%s\t%s\t%" PRIu8 "\t%s\r\n", index + 1, furi_string_get_cstr(token_info->name), token_info_get_algo_as_cstr(token_info), token_info->digits, - token_info->duration); + token_info_get_type_as_cstr(token_info)); } void list_output_formatter_print_footer_tsv() { -} \ No newline at end of file +} diff --git a/cli/plugins/modify/add/add.c b/cli/plugins/modify/add/add.c index f0b10a7d56c..46ff9f468e0 100644 --- a/cli/plugins/modify/add/add.c +++ b/cli/plugins/modify/add/add.c @@ -43,7 +43,9 @@ static TotpIteratorUpdateTokenResult !totp_cli_try_read_unsecure_flag(temp_str, &parsed, &mask_user_input) && !totp_cli_try_read_automation_features(token_info, temp_str, context_t->args, &parsed) && !totp_cli_try_read_plain_token_secret_encoding( - temp_str, context_t->args, &parsed, &token_secret_encoding)) { + temp_str, context_t->args, &parsed, &token_secret_encoding) && + !totp_cli_try_read_token_type(token_info, temp_str, context_t->args, &parsed) && + !totp_cli_try_read_token_counter(token_info, temp_str, context_t->args, &parsed)) { totp_cli_printf_unknown_argument(temp_str); } @@ -123,4 +125,4 @@ static const FlipperAppPluginDescriptor plugin_descriptor = { const FlipperAppPluginDescriptor* totp_cli_add_plugin_ep() { return &plugin_descriptor; -} \ No newline at end of file +} diff --git a/cli/plugins/modify/common.c b/cli/plugins/modify/common.c index 60d30b2541e..ee7cf9fb44d 100644 --- a/cli/plugins/modify/common.c +++ b/cli/plugins/modify/common.c @@ -133,4 +133,50 @@ bool totp_cli_try_read_plain_token_secret_encoding( } return false; -} \ No newline at end of file +} + +bool totp_cli_try_read_token_type( + TokenInfo* token_info, + FuriString* arg, + FuriString* args, + bool* parsed) { + if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_TYPE_PREFIX) == 0) { + if(!args_read_string_and_trim(args, arg)) { + totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_TYPE_PREFIX); + } else if(!token_info_set_token_type_from_str(token_info, arg)) { + TOTP_CLI_PRINTF_ERROR( + "\"%s\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ARG_TYPE_PREFIX + "\"\r\n", + furi_string_get_cstr(arg)); + } else { + *parsed = true; + } + + return true; + } + + return false; +} + +bool totp_cli_try_read_token_counter( + TokenInfo* token_info, + FuriString* arg, + FuriString* args, + bool* parsed) { + if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_COUNTER_PREFIX) == 0) { + if(!args_read_string_and_trim(args, arg)) { + totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_COUNTER_PREFIX); + } else if(!token_info_set_token_counter_from_str(token_info, arg)) { + TOTP_CLI_PRINTF_ERROR( + "\"%s\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ARG_COUNTER_PREFIX + "\"\r\n", + furi_string_get_cstr(arg)); + } else { + *parsed = true; + } + + return true; + } + + return false; +} diff --git a/cli/plugins/modify/common.h b/cli/plugins/modify/common.h index 9938e18a4a2..92e36ad5371 100644 --- a/cli/plugins/modify/common.h +++ b/cli/plugins/modify/common.h @@ -6,20 +6,15 @@ extern "C" { #endif -#define TOTP_CLI_COMMAND_ARG_NAME "name" #define TOTP_CLI_COMMAND_ARG_NAME_PREFIX "-n" -#define TOTP_CLI_COMMAND_ARG_ALGO "algo" #define TOTP_CLI_COMMAND_ARG_ALGO_PREFIX "-a" -#define TOTP_CLI_COMMAND_ARG_DIGITS "digits" #define TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX "-d" #define TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX "-u" -#define TOTP_CLI_COMMAND_ARG_DURATION "duration" #define TOTP_CLI_COMMAND_ARG_DURATION_PREFIX "-l" #define TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX "-b" -#define TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE "feature" -#define TOTP_CLI_COMMAND_ARG_INDEX "index" #define TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX "-e" -#define TOTP_CLI_COMMAND_ARG_SECRET_ENCODING "encoding" +#define TOTP_CLI_COMMAND_ARG_TYPE_PREFIX "-t" +#define TOTP_CLI_COMMAND_ARG_COUNTER_PREFIX "-i" /** * @brief Tries to read token hashing algo @@ -96,6 +91,34 @@ bool totp_cli_try_read_plain_token_secret_encoding( bool* parsed, PlainTokenSecretEncoding* secret_encoding); +/** + * @brief Tries to read token type + * @param token_info token info to set parsed token type to if successfully read and parsed + * @param arg argument to parse + * @param args rest of arguments + * @param parsed will be set to \c true if token type sucecssfully read and parsed; \c false otherwise + * @return \c true if \c arg represents token type argument; \c false otherwise + */ +bool totp_cli_try_read_token_type( + TokenInfo* token_info, + FuriString* arg, + FuriString* args, + bool* parsed); + +/** + * @brief Tries to read token counter + * @param token_info token info to set parsed token counter to if successfully read and parsed + * @param arg argument to parse + * @param args rest of arguments + * @param parsed will be set to \c true if token counter sucecssfully read and parsed; \c false otherwise + * @return \c true if \c arg represents token counter argument; \c false otherwise + */ +bool totp_cli_try_read_token_counter( + TokenInfo* token_info, + FuriString* arg, + FuriString* args, + bool* parsed); + #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/cli/plugins/modify/update/update.c b/cli/plugins/modify/update/update.c index 1311c80633a..45f532b280a 100644 --- a/cli/plugins/modify/update/update.c +++ b/cli/plugins/modify/update/update.c @@ -71,7 +71,9 @@ static TotpIteratorUpdateTokenResult !totp_cli_try_read_change_secret_flag(temp_str, &parsed, &update_token_secret) && !totp_cli_try_read_automation_features(token_info, temp_str, context_t->args, &parsed) && !totp_cli_try_read_plain_token_secret_encoding( - temp_str, context_t->args, &parsed, &token_secret_encoding)) { + temp_str, context_t->args, &parsed, &token_secret_encoding) && + !totp_cli_try_read_token_type(token_info, temp_str, context_t->args, &parsed) && + !totp_cli_try_read_token_counter(token_info, temp_str, context_t->args, &parsed)) { totp_cli_printf_unknown_argument(temp_str); } @@ -162,4 +164,4 @@ static const FlipperAppPluginDescriptor plugin_descriptor = { const FlipperAppPluginDescriptor* totp_cli_update_plugin_ep() { return &plugin_descriptor; -} \ No newline at end of file +} diff --git a/services/config/constants.h b/services/config/constants.h index 97c8f0d0a5a..a132f2180f3 100644 --- a/services/config/constants.h +++ b/services/config/constants.h @@ -4,7 +4,7 @@ #define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/totp") #define CONFIG_FILE_HEADER "Flipper TOTP plugin config file" -#define CONFIG_FILE_ACTUAL_VERSION (9) +#define CONFIG_FILE_ACTUAL_VERSION (10) #define TOTP_CONFIG_KEY_TIMEZONE "Timezone" #define TOTP_CONFIG_KEY_TOKEN_NAME "TokenName" @@ -13,6 +13,8 @@ #define TOTP_CONFIG_KEY_TOKEN_DIGITS "TokenDigits" #define TOTP_CONFIG_KEY_TOKEN_DURATION "TokenDuration" #define TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES "TokenAutomationFeatures" +#define TOTP_CONFIG_KEY_TOKEN_TYPE "TokenType" +#define TOTP_CONFIG_KEY_TOKEN_COUNTER "TokenCounter" #define TOTP_CONFIG_KEY_CRYPTO_VERIFY "Crypto" #define TOTP_CONFIG_KEY_SALT "Salt" #define TOTP_CONFIG_KEY_PINSET "PinIsSet" diff --git a/services/config/migrations/common_migration.c b/services/config/migrations/common_migration.c index 33a441e724c..397c72c4604 100644 --- a/services/config/migrations/common_migration.c +++ b/services/config/migrations/common_migration.c @@ -182,6 +182,26 @@ bool totp_config_migrate_to_latest( &default_automation_features, 1); } + + if(current_version > 9) { + flipper_format_read_string( + fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_TYPE, temp_str); + flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_TYPE, temp_str); + flipper_format_read_string( + fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_COUNTER, temp_str); + flipper_format_write_string( + fff_data_file, TOTP_CONFIG_KEY_TOKEN_COUNTER, temp_str); + } else { + const uint32_t default_token_type = TokenTypeTOTP; + flipper_format_write_uint32( + fff_data_file, TOTP_CONFIG_KEY_TOKEN_TYPE, &default_token_type, 1); + const uint64_t default_counter = 0; + flipper_format_write_hex( + fff_data_file, + TOTP_CONFIG_KEY_TOKEN_COUNTER, + (const uint8_t*)&default_counter, + sizeof(default_counter)); + } } Stream* stream = flipper_format_get_raw_stream(fff_data_file); @@ -196,4 +216,4 @@ bool totp_config_migrate_to_latest( furi_string_free(temp_str); return result; -} \ No newline at end of file +} diff --git a/services/config/token_info_iterator.c b/services/config/token_info_iterator.c index 3d47b9616c8..86823707c8d 100644 --- a/services/config/token_info_iterator.c +++ b/services/config/token_info_iterator.c @@ -199,6 +199,19 @@ static bool break; } + tmp_uint32 = token_info->type; + if(!flipper_format_write_uint32(temp_ff, TOTP_CONFIG_KEY_TOKEN_TYPE, &tmp_uint32, 1)) { + break; + } + + if(!flipper_format_write_hex( + temp_ff, + TOTP_CONFIG_KEY_TOKEN_COUNTER, + (uint8_t*)&token_info->counter, + sizeof(token_info->counter))) { + break; + } + Stream* temp_stream = flipper_format_get_raw_stream(temp_ff); if(!stream_rewind(temp_stream)) { @@ -398,6 +411,54 @@ TotpIteratorUpdateTokenResult totp_token_info_iterator_update_current_token( return result; } +TotpIteratorUpdateTokenResult + totp_token_info_iterator_current_token_inc_counter(TokenInfoIteratorContext* context) { + if(!seek_to_token(context->current_index, context)) { + return TotpIteratorUpdateTokenResultFileUpdateFailed; + } + + Stream* stream = flipper_format_get_raw_stream(context->config_file); + + size_t offset_start = stream_tell(stream); + + TokenInfo* token_info = context->current_token; + token_info->counter++; + + char buffer[sizeof(TOTP_CONFIG_KEY_TOKEN_COUNTER) + 1]; + bool found = false; + while(!found) { + if(!stream_seek_to_char(stream, '\n', StreamDirectionForward)) { + break; + } + + size_t buffer_read_size; + if((buffer_read_size = stream_read(stream, (uint8_t*)&buffer[0], sizeof(buffer))) == 0) { + break; + } + + if(!stream_seek(stream, -(int32_t)buffer_read_size, StreamOffsetFromCurrent)) { + break; + } + + if(strncmp(buffer, "\n" TOTP_CONFIG_KEY_TOKEN_COUNTER ":", sizeof(buffer)) == 0) { + found = true; + } + } + + TotpIteratorUpdateTokenResult result = TotpIteratorUpdateTokenResultFileUpdateFailed; + if(found && stream_seek(stream, 1, StreamOffsetFromCurrent) && + flipper_format_write_hex( + context->config_file, + TOTP_CONFIG_KEY_TOKEN_COUNTER, + (uint8_t*)&token_info->counter, + sizeof(token_info->counter))) { + result = TotpIteratorUpdateTokenResultSuccess; + } + + stream_seek(stream, offset_start, StreamOffsetFromStart); + return result; +} + TotpIteratorUpdateTokenResult totp_token_info_iterator_add_new_token( TokenInfoIteratorContext* context, TOTP_ITERATOR_UPDATE_TOKEN_ACTION update, @@ -521,6 +582,21 @@ bool totp_token_info_iterator_go_to(TokenInfoIteratorContext* context, size_t to tokenInfo->automation_features = TokenAutomationFeatureNone; } + if(flipper_format_read_uint32( + context->config_file, TOTP_CONFIG_KEY_TOKEN_TYPE, &temp_data32, 1)) { + tokenInfo->type = temp_data32; + } else { + tokenInfo->type = TokenTypeTOTP; + } + + if(!flipper_format_read_hex( + context->config_file, + TOTP_CONFIG_KEY_TOKEN_COUNTER, + (uint8_t*)&tokenInfo->counter, + sizeof(tokenInfo->counter))) { + tokenInfo->counter = 0; + } + stream_seek(stream, original_offset, StreamOffsetFromStart); if(token_update_needed && !totp_token_info_iterator_save_current_token_info_changes(context)) { @@ -549,4 +625,4 @@ void totp_token_info_iterator_attach_to_config_file( context->config_file = config_file; Stream* stream = flipper_format_get_raw_stream(context->config_file); stream_seek(stream, context->last_seek_offset, StreamOffsetFromStart); -} \ No newline at end of file +} diff --git a/services/config/token_info_iterator.h b/services/config/token_info_iterator.h index bcd35803abc..af7995d4dae 100644 --- a/services/config/token_info_iterator.h +++ b/services/config/token_info_iterator.h @@ -70,6 +70,14 @@ TotpIteratorUpdateTokenResult totp_token_info_iterator_update_current_token( TOTP_ITERATOR_UPDATE_TOKEN_ACTION update, const void* update_context); +/** + * @brief Increments token counter + * @param context token info iterator context + * @return \c true if operation succeeded; \c false otherwise + */ +TotpIteratorUpdateTokenResult + totp_token_info_iterator_current_token_inc_counter(TokenInfoIteratorContext* context); + /** * @brief Adds new token info to the end of the list using given update action * @param context token info iterator context diff --git a/services/totp/totp.c b/services/totp/totp.c index 32232f21a7c..9301bbb5153 100644 --- a/services/totp/totp.c +++ b/services/totp/totp.c @@ -72,6 +72,14 @@ uint64_t totp_at( algo, plain_secret, plain_secret_length, totp_timecode(interval, for_time_adjusted)); } +uint64_t hotp_at( + TOTP_ALGO algo, + const uint8_t* plain_secret, + size_t plain_secret_length, + uint64_t counter) { + return otp_generate(algo, plain_secret, plain_secret_length, counter); +} + static int totp_algo_common( int type, const uint8_t* key, diff --git a/services/totp/totp.h b/services/totp/totp.h index d578f6ea99d..d570f7cd915 100644 --- a/services/totp/totp.h +++ b/services/totp/totp.h @@ -37,7 +37,7 @@ extern const TOTP_ALGO TOTP_ALGO_SHA256; extern const TOTP_ALGO TOTP_ALGO_SHA512; /** - * @brief Generates a OTP key using the totp algorithm. + * @brief Generates a TOTP key using the totp algorithm. * @param algo hashing algorithm to be used * @param plain_secret plain token secret * @param plain_secret_length plain token secret length @@ -53,3 +53,17 @@ uint64_t totp_at( uint64_t for_time, float timezone, uint8_t interval); + +/** + * @brief Generates a HOTP key using the hotp algorithm. + * @param algo hashing algorithm to be used + * @param plain_secret plain token secret + * @param plain_secret_length plain token secret length + * @param counter the HOTP counter + * @return HOTP code if code was successfully generated; 0 otherwise + */ +uint64_t hotp_at( + TOTP_ALGO algo, + const uint8_t* plain_secret, + size_t plain_secret_length, + uint64_t counter); diff --git a/types/token_info.c b/types/token_info.c index 1d1e7316066..98e3d17d0ad 100644 --- a/types/token_info.c +++ b/types/token_info.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "common.h" #include "../services/crypto/crypto_facade.h" @@ -183,6 +184,37 @@ bool token_info_set_automation_feature_from_str(TokenInfo* token_info, const Fur return false; } +bool token_info_set_token_type_from_str(TokenInfo* token_info, const FuriString* str) { + if(furi_string_cmpi_str(str, TOKEN_TYPE_TOTP_NAME) == 0) { + token_info->type = TokenTypeTOTP; + return true; + } + + if(furi_string_cmpi_str(str, TOKEN_TYPE_HOTP_NAME) == 0) { + token_info->type = TokenTypeHOTP; + return true; + } + + return false; +} + +const char* token_info_get_type_as_cstr(const TokenInfo* token_info) { + switch(token_info->type) { + case TokenTypeTOTP: + return TOKEN_TYPE_TOTP_NAME; + case TokenTypeHOTP: + return TOKEN_TYPE_HOTP_NAME; + default: + break; + } + + return NULL; +} + +bool token_info_set_token_counter_from_str(TokenInfo* token_info, const FuriString* str) { + return sscanf(furi_string_get_cstr(str), "%" PRIu64, &token_info->counter) == 1; +} + TokenInfo* token_info_clone(const TokenInfo* src) { TokenInfo* clone = token_info_alloc(); memcpy(clone, src, sizeof(TokenInfo)); @@ -203,5 +235,7 @@ void token_info_set_defaults(TokenInfo* token_info) { token_info->digits = TokenDigitsCountDefault; token_info->duration = TokenDurationDefault; token_info->automation_features = TokenAutomationFeatureNone; + token_info->type = TokenTypeTOTP; + token_info->counter = 0; furi_string_reset(token_info->name); -} \ No newline at end of file +} diff --git a/types/token_info.h b/types/token_info.h index 84eaea28b4d..c0dc6d7d670 100644 --- a/types/token_info.h +++ b/types/token_info.h @@ -15,6 +15,8 @@ #define TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END_NAME "enter" #define TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END_NAME "tab" #define TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER_NAME "slower" +#define TOKEN_TYPE_TOTP_NAME "totp" +#define TOKEN_TYPE_HOTP_NAME "hotp" #ifdef __cplusplus extern "C" { @@ -25,6 +27,7 @@ typedef uint8_t TokenDigitsCount; typedef uint8_t TokenDuration; typedef uint8_t TokenAutomationFeature; typedef uint8_t PlainTokenSecretEncoding; +typedef uint8_t TokenType; /** * @brief Hashing algorithm to be used to generate token @@ -147,6 +150,22 @@ enum PlainTokenSecretEncodings { PlainTokenSecretEncodingBase64 = 1 }; +/** + * @brief Token types + */ +enum TokenTypes { + + /** + * @brief Time-based One-time Password token type + */ + TokenTypeTOTP = 0, + + /** + * @brief HMAC-Based One-Time Password token type + */ + TokenTypeHOTP = 1 +}; + /** * @brief TOTP token information */ @@ -185,6 +204,16 @@ typedef struct { * @brief Token input automation features */ TokenAutomationFeature automation_features; + + /** + * @brief Token type + */ + TokenType type; + + /** + * @brief HOTP counter + */ + uint64_t counter; } TokenInfo; /** @@ -248,8 +277,8 @@ bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str); bool token_info_set_algo_from_int(TokenInfo* token_info, uint8_t algo_code); /** - * @brief Gets token hahsing algorithm name as C-string - * @param token_info instance which token hahsing algorithm name should be returned + * @brief Gets token hashing algorithm name as C-string + * @param token_info instance which token hashing algorithm name should be returned * @return token hashing algorithm name as C-string */ const char* token_info_get_algo_as_cstr(const TokenInfo* token_info); @@ -262,6 +291,29 @@ const char* token_info_get_algo_as_cstr(const TokenInfo* token_info); */ bool token_info_set_automation_feature_from_str(TokenInfo* token_info, const FuriString* str); +/** + * @brief Sets token type from \c str value + * @param token_info instance whichs token type should be updated + * @param str desired token type + * @return \c true if token type has been set; \c false otherwise + */ +bool token_info_set_token_type_from_str(TokenInfo* token_info, const FuriString* str); + +/** + * @brief Gets token type as C-string + * @param token_info instance which token type should be returned + * @return token type as C-string + */ +const char* token_info_get_type_as_cstr(const TokenInfo* token_info); + +/** + * @brief Sets token counter from \c str value + * @param token_info instance whichs token counter should be updated + * @param str desired token counter + * @return \c true if token counter has been set; \c false otherwise + */ +bool token_info_set_token_counter_from_str(TokenInfo* token_info, const FuriString* str); + /** * @brief Clones \c TokenInfo instance * @param src instance to clone diff --git a/ui/scenes/add_new_token/totp_scene_add_new_token.c b/ui/scenes/add_new_token/totp_scene_add_new_token.c index ae0a7bd488b..d16d9f0d30b 100644 --- a/ui/scenes/add_new_token/totp_scene_add_new_token.c +++ b/ui/scenes/add_new_token/totp_scene_add_new_token.c @@ -12,6 +12,8 @@ char* TOKEN_ALGO_LIST[] = {"SHA1", "SHA256", "SHA512", "Steam"}; char* TOKEN_DIGITS_TEXT_LIST[] = {"5 digits", "6 digits", "8 digits"}; +char* TOKEN_TYPE_LIST[] = {"Time-based (TOTP)", "Counter-based (HOTP)"}; + TokenDigitsCount TOKEN_DIGITS_VALUE_LIST[] = { TokenDigitsCountFive, TokenDigitsCountSix, @@ -20,9 +22,10 @@ TokenDigitsCount TOKEN_DIGITS_VALUE_LIST[] = { typedef enum { TokenNameTextBox, TokenSecretTextBox, + TokenTypeSelect, TokenAlgoSelect, TokenLengthSelect, - TokenDurationSelect, + TokenDurationOrCounterSelect, ConfirmButton, } Control; @@ -37,7 +40,10 @@ typedef struct { TokenHashAlgo algo; uint8_t digits_count_index; uint8_t duration; + char* initial_counter; + size_t initial_counter_length; FuriString* duration_text; + TokenType type; } SceneState; struct TotpAddContext { @@ -45,7 +51,10 @@ struct TotpAddContext { const CryptoSettings* crypto_settings; }; -enum TotpIteratorUpdateTokenResultsEx { TotpIteratorUpdateTokenResultInvalidSecret = 1 }; +enum TotpIteratorUpdateTokenResultsEx { + TotpIteratorUpdateTokenResultInvalidSecret = 1, + TotpIteratorUpdateTokenResultInvalidCounter = 2 +}; static void update_duration_text(SceneState* scene_state) { furi_string_printf(scene_state->duration_text, "%d sec.", scene_state->duration); @@ -53,6 +62,10 @@ static void update_duration_text(SceneState* scene_state) { static TotpIteratorUpdateTokenResult add_token_handler(TokenInfo* tokenInfo, const void* context) { const struct TotpAddContext* context_t = context; + if(sscanf(context_t->scene_state->initial_counter, "%" PRIu64, &tokenInfo->counter) != 1) { + return TotpIteratorUpdateTokenResultInvalidCounter; + } + if(!token_info_set_secret( tokenInfo, context_t->scene_state->token_secret, @@ -69,6 +82,7 @@ static TotpIteratorUpdateTokenResult add_token_handler(TokenInfo* tokenInfo, con tokenInfo->algo = context_t->scene_state->algo; tokenInfo->digits = TOKEN_DIGITS_VALUE_LIST[context_t->scene_state->digits_count_index]; tokenInfo->duration = context_t->scene_state->duration; + tokenInfo->type = context_t->scene_state->type; return TotpIteratorUpdateTokenResultSuccess; } @@ -93,6 +107,33 @@ static void ask_user_input( } } +static void update_screen_y_offset(SceneState* scene_state) { + if(scene_state->selected_control > TokenLengthSelect) { + scene_state->screen_y_offset = 68; + } else if(scene_state->selected_control > TokenAlgoSelect) { + scene_state->screen_y_offset = 51; + } else if(scene_state->selected_control > TokenTypeSelect) { + scene_state->screen_y_offset = 34; + } else if(scene_state->selected_control > TokenSecretTextBox) { + scene_state->screen_y_offset = 17; + } else { + scene_state->screen_y_offset = 0; + } +} + +static void + show_invalid_field_message(const PluginState* plugin_state, Control control, const char* text) { + DialogMessage* message = dialog_message_alloc(); + SceneState* scene_state = plugin_state->current_scene_state; + dialog_message_set_buttons(message, "Back", NULL, NULL); + dialog_message_set_text( + message, text, SCREEN_WIDTH_CENTER, SCREEN_HEIGHT_CENTER, AlignCenter, AlignCenter); + dialog_message_show(plugin_state->dialogs_app, message); + dialog_message_free(message); + scene_state->selected_control = control; + update_screen_y_offset(scene_state); +} + void totp_scene_add_new_token_activate(PluginState* plugin_state) { SceneState* scene_state = malloc(sizeof(SceneState)); furi_check(scene_state != NULL); @@ -101,6 +142,8 @@ void totp_scene_add_new_token_activate(PluginState* plugin_state) { scene_state->token_name_length = strlen(scene_state->token_name); scene_state->token_secret = "Secret"; scene_state->token_secret_length = strlen(scene_state->token_secret); + scene_state->initial_counter = "Counter"; + scene_state->initial_counter_length = strlen(scene_state->initial_counter); scene_state->screen_y_offset = 0; @@ -108,6 +151,7 @@ void totp_scene_add_new_token_activate(PluginState* plugin_state) { scene_state->duration = TokenDurationDefault; scene_state->duration_text = furi_string_alloc(); + scene_state->type = TokenTypeTOTP; update_duration_text(scene_state); } @@ -124,33 +168,50 @@ void totp_scene_add_new_token_render(Canvas* const canvas, const PluginState* pl 27 - scene_state->screen_y_offset, scene_state->token_secret, scene_state->selected_control == TokenSecretTextBox); + ui_control_select_render( canvas, 0, 44 - scene_state->screen_y_offset, SCREEN_WIDTH, - TOKEN_ALGO_LIST[scene_state->algo], - scene_state->selected_control == TokenAlgoSelect); + TOKEN_TYPE_LIST[scene_state->type], + scene_state->selected_control == TokenTypeSelect); + ui_control_select_render( canvas, 0, 61 - scene_state->screen_y_offset, SCREEN_WIDTH, - TOKEN_DIGITS_TEXT_LIST[scene_state->digits_count_index], - scene_state->selected_control == TokenLengthSelect); - + TOKEN_ALGO_LIST[scene_state->algo], + scene_state->selected_control == TokenAlgoSelect); ui_control_select_render( canvas, 0, 78 - scene_state->screen_y_offset, SCREEN_WIDTH, - furi_string_get_cstr(scene_state->duration_text), - scene_state->selected_control == TokenDurationSelect); + TOKEN_DIGITS_TEXT_LIST[scene_state->digits_count_index], + scene_state->selected_control == TokenLengthSelect); + + if(scene_state->type == TokenTypeTOTP) { + ui_control_select_render( + canvas, + 0, + 95 - scene_state->screen_y_offset, + SCREEN_WIDTH, + furi_string_get_cstr(scene_state->duration_text), + scene_state->selected_control == TokenDurationOrCounterSelect); + } else { + ui_control_text_box_render( + canvas, + 95 - scene_state->screen_y_offset, + scene_state->initial_counter, + scene_state->selected_control == TokenDurationOrCounterSelect); + } ui_control_button_render( canvas, SCREEN_WIDTH_CENTER - 24, - 101 - scene_state->screen_y_offset, + 119 - scene_state->screen_y_offset, 48, 13, "Confirm", @@ -164,18 +225,6 @@ void totp_scene_add_new_token_render(Canvas* const canvas, const PluginState* pl canvas_set_font(canvas, FontSecondary); } -void update_screen_y_offset(SceneState* scene_state) { - if(scene_state->selected_control > TokenLengthSelect) { - scene_state->screen_y_offset = 51; - } else if(scene_state->selected_control > TokenAlgoSelect) { - scene_state->screen_y_offset = 34; - } else if(scene_state->selected_control > TokenSecretTextBox) { - scene_state->screen_y_offset = 17; - } else { - scene_state->screen_y_offset = 0; - } -} - bool totp_scene_add_new_token_handle_event( const PluginEvent* const event, PluginState* plugin_state) { @@ -216,10 +265,14 @@ bool totp_scene_add_new_token_handle_event( } else if(scene_state->selected_control == TokenLengthSelect) { totp_roll_value_uint8_t( &scene_state->digits_count_index, 1, 0, 2, RollOverflowBehaviorRoll); - } else if(scene_state->selected_control == TokenDurationSelect) { + } else if( + scene_state->selected_control == TokenDurationOrCounterSelect && + scene_state->type == TokenTypeTOTP) { totp_roll_value_uint8_t( &scene_state->duration, 15, 15, 255, RollOverflowBehaviorStop); update_duration_text(scene_state); + } else if(scene_state->selected_control == TokenTypeSelect) { + totp_roll_value_uint8_t(&scene_state->type, 1, 0, 1, RollOverflowBehaviorRoll); } break; case InputKeyLeft: @@ -233,10 +286,14 @@ bool totp_scene_add_new_token_handle_event( } else if(scene_state->selected_control == TokenLengthSelect) { totp_roll_value_uint8_t( &scene_state->digits_count_index, -1, 0, 2, RollOverflowBehaviorRoll); - } else if(scene_state->selected_control == TokenDurationSelect) { + } else if( + scene_state->selected_control == TokenDurationOrCounterSelect && + scene_state->type == TokenTypeTOTP) { totp_roll_value_uint8_t( &scene_state->duration, -15, 15, 255, RollOverflowBehaviorStop); update_duration_text(scene_state); + } else if(scene_state->selected_control == TokenTypeSelect) { + totp_roll_value_uint8_t(&scene_state->type, -1, 0, 1, RollOverflowBehaviorRoll); } break; case InputKeyOk: @@ -247,7 +304,7 @@ bool totp_scene_add_new_token_handle_event( default: break; } - } else if(event->input.type == InputTypeRelease && event->input.key == InputKeyOk) { + } else if(event->input.type == InputTypeShort && event->input.key == InputKeyOk) { switch(scene_state->selected_control) { case TokenNameTextBox: ask_user_input( @@ -267,7 +324,14 @@ bool totp_scene_add_new_token_handle_event( break; case TokenLengthSelect: break; - case TokenDurationSelect: + case TokenDurationOrCounterSelect: + if(scene_state->type == TokenTypeHOTP) { + ask_user_input( + plugin_state, + "Initial counter", + &scene_state->initial_counter, + &scene_state->initial_counter_length); + } break; case ConfirmButton: { struct TotpAddContext add_context = { @@ -280,19 +344,11 @@ bool totp_scene_add_new_token_handle_event( if(add_result == TotpIteratorUpdateTokenResultSuccess) { totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken); } else if(add_result == TotpIteratorUpdateTokenResultInvalidSecret) { - DialogMessage* message = dialog_message_alloc(); - dialog_message_set_buttons(message, "Back", NULL, NULL); - dialog_message_set_text( - message, - "Token secret is invalid", - SCREEN_WIDTH_CENTER, - SCREEN_HEIGHT_CENTER, - AlignCenter, - AlignCenter); - dialog_message_show(plugin_state->dialogs_app, message); - dialog_message_free(message); - scene_state->selected_control = TokenSecretTextBox; - update_screen_y_offset(scene_state); + show_invalid_field_message( + plugin_state, TokenSecretTextBox, "Token secret is invalid"); + } else if(add_result == TotpIteratorUpdateTokenResultInvalidCounter) { + show_invalid_field_message( + plugin_state, TokenDurationOrCounterSelect, "Initial counter is invalid"); } else if(add_result == TotpIteratorUpdateTokenResultFileUpdateFailed) { totp_dialogs_config_updating_error(plugin_state); } diff --git a/ui/scenes/app_settings/totp_app_settings.c b/ui/scenes/app_settings/totp_app_settings.c index 01f5a4a0b65..69a9fb2bcfa 100644 --- a/ui/scenes/app_settings/totp_app_settings.c +++ b/ui/scenes/app_settings/totp_app_settings.c @@ -345,7 +345,7 @@ bool totp_scene_app_settings_handle_event( break; } } else if( - event->input.type == InputTypeRelease && event->input.key == InputKeyOk && + event->input.type == InputTypeShort && event->input.key == InputKeyOk && scene_state->selected_control == ConfirmButton) { plugin_state->timezone_offset = (float)scene_state->tz_offset_hours + (float)scene_state->tz_offset_minutes / 60.0f; diff --git a/ui/scenes/authenticate/totp_scene_authenticate.c b/ui/scenes/authenticate/totp_scene_authenticate.c index ea407395a2d..2a6f7cab070 100644 --- a/ui/scenes/authenticate/totp_scene_authenticate.c +++ b/ui/scenes/authenticate/totp_scene_authenticate.c @@ -84,7 +84,7 @@ bool totp_scene_authenticate_handle_event( } SceneState* scene_state = plugin_state->current_scene_state; - if(event->input.type == InputTypePress) { + if(event->input.type == InputTypeShort) { switch(event->input.key) { case InputKeyUp: if(scene_state->code_length < MAX_CODE_LENGTH) { diff --git a/ui/scenes/generate_token/totp_scene_generate_token.c b/ui/scenes/generate_token/totp_scene_generate_token.c index e4c1da938f7..9d019513a59 100644 --- a/ui/scenes/generate_token/totp_scene_generate_token.c +++ b/ui/scenes/generate_token/totp_scene_generate_token.c @@ -255,8 +255,8 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_ const SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; canvas_set_font(canvas, FontPrimary); - const char* token_name_cstr = - furi_string_get_cstr(totp_token_info_iterator_get_current_token(iterator_context)->name); + const TokenInfo* token_info = totp_token_info_iterator_get_current_token(iterator_context); + const char* token_name_cstr = furi_string_get_cstr(token_info->name); uint16_t token_name_width = canvas_string_width(canvas, token_name_cstr); if(SCREEN_WIDTH - token_name_width > 18) { canvas_draw_str_aligned( @@ -277,12 +277,21 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_ draw_totp_code(canvas, plugin_state); - canvas_draw_box( - canvas, - scene_state->ui_precalculated_dimensions.progress_bar_x, - SCREEN_HEIGHT - PROGRESS_BAR_MARGIN - PROGRESS_BAR_HEIGHT, - scene_state->ui_precalculated_dimensions.progress_bar_width, - PROGRESS_BAR_HEIGHT); + if(token_info->type == TokenTypeTOTP) { + canvas_draw_box( + canvas, + scene_state->ui_precalculated_dimensions.progress_bar_x, + SCREEN_HEIGHT - PROGRESS_BAR_MARGIN - PROGRESS_BAR_HEIGHT, + scene_state->ui_precalculated_dimensions.progress_bar_width, + PROGRESS_BAR_HEIGHT); + } else { + char buffer[21]; + snprintf(&buffer[0], sizeof(buffer), "%" PRIu64, token_info->counter); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, SCREEN_WIDTH_CENTER, SCREEN_HEIGHT - 5, AlignCenter, AlignCenter, buffer); + } + if(totp_token_info_iterator_get_total_count(iterator_context) > 1) { canvas_draw_icon(canvas, 0, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_left_8x9); canvas_draw_icon( @@ -359,6 +368,21 @@ bool totp_scene_generate_token_handle_event( scene_state->notification_app, get_notification_sequence_automation(plugin_state, scene_state)); return true; + } else if(event->input.key == InputKeyOk) { + TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + const TokenInfo* token_info = + totp_token_info_iterator_get_current_token(iterator_context); + if(token_info->type == TokenTypeHOTP) { + scene_state = (SceneState*)plugin_state->current_scene_state; + totp_token_info_iterator_current_token_inc_counter(iterator_context); + totp_generate_code_worker_notify( + scene_state->generate_code_worker_context, + TotpGenerateCodeWorkerEventForceUpdate); + notification_message( + scene_state->notification_app, + get_notification_sequence_new_token(plugin_state, scene_state)); + } } #endif } else if(event->input.type == InputTypePress || event->input.type == InputTypeRepeat) { @@ -404,7 +428,7 @@ bool totp_scene_generate_token_handle_event( default: break; } - } else if(event->input.type == InputTypeRelease && event->input.key == InputKeyOk) { + } else if(event->input.type == InputTypeShort && event->input.key == InputKeyOk) { totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu); } diff --git a/ui/scenes/token_menu/totp_scene_token_menu.c b/ui/scenes/token_menu/totp_scene_token_menu.c index 7eb4ea87cce..d203c86fc26 100644 --- a/ui/scenes/token_menu/totp_scene_token_menu.c +++ b/ui/scenes/token_menu/totp_scene_token_menu.c @@ -125,7 +125,7 @@ bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginSt default: break; } - } else if(event->input.type == InputTypeRelease && event->input.key == InputKeyOk) { + } else if(event->input.type == InputTypeShort && event->input.key == InputKeyOk) { switch(scene_state->selected_control) { case AddNewToken: { #ifdef TOTP_UI_ADD_NEW_TOKEN_ENABLED diff --git a/version.h b/version.h index 1325e062ef1..e92e0a8c87a 100644 --- a/version.h +++ b/version.h @@ -1,5 +1,5 @@ #pragma once #define TOTP_APP_VERSION_MAJOR (5) -#define TOTP_APP_VERSION_MINOR (0) +#define TOTP_APP_VERSION_MINOR (5) #define TOTP_APP_VERSION_PATCH (0) \ No newline at end of file diff --git a/workers/generate_totp_code/generate_totp_code.c b/workers/generate_totp_code/generate_totp_code.c index 20a7bb54c47..94f8e91f2b8 100644 --- a/workers/generate_totp_code/generate_totp_code.c +++ b/workers/generate_totp_code/generate_totp_code.c @@ -72,17 +72,23 @@ static void generate_totp_code( uint8_t* key = totp_crypto_decrypt( token_info->token, token_info->token_length, context->crypto_settings, &key_length); - int_token_to_str( - totp_at( + uint64_t otp_code; + if(token_info->type == TokenTypeTOTP) { + otp_code = totp_at( get_totp_algo_impl(token_info->algo), key, key_length, current_ts, context->timezone_offset, - token_info->duration), - context->code_buffer, - token_info->digits, - token_info->algo); + token_info->duration); + } else if(token_info->type == TokenTypeHOTP) { + otp_code = hotp_at( + get_totp_algo_impl(token_info->algo), key, key_length, token_info->counter); + } else { + furi_crash("Unknown token type"); + } + + int_token_to_str(otp_code, context->code_buffer, token_info->digits, token_info->algo); memset_s(key, key_length, 0, key_length); free(key); } else { @@ -116,14 +122,18 @@ static int32_t totp_generate_worker_callback(void* context) { continue; } - uint32_t curr_ts = furi_hal_rtc_get_timestamp(); + const bool is_time_based = token_info->type == TokenTypeTOTP; + + uint32_t curr_ts = is_time_based ? furi_hal_rtc_get_timestamp() : 0; bool time_left = false; if(flags & TotpGenerateCodeWorkerEventForceUpdate || - (time_left = (curr_ts % token_info->duration) == 0)) { + (is_time_based && (time_left = (curr_ts % token_info->duration) == 0))) { if(furi_mutex_acquire(t_context->code_buffer_sync, FuriWaitForever) == FuriStatusOk) { generate_totp_code(t_context, token_info, curr_ts); - curr_ts = furi_hal_rtc_get_timestamp(); + if(is_time_based) { + curr_ts = furi_hal_rtc_get_timestamp(); + } furi_mutex_release(t_context->code_buffer_sync); if(t_context->on_new_code_generated_handler != NULL) { (*(t_context->on_new_code_generated_handler))( @@ -132,7 +142,8 @@ static int32_t totp_generate_worker_callback(void* context) { } } - if(t_context->on_code_lifetime_changed_handler != NULL) { + if(t_context->on_code_lifetime_changed_handler != NULL && + token_info->type == TokenTypeTOTP) { (*(t_context->on_code_lifetime_changed_handler))( (float)(token_info->duration - curr_ts % token_info->duration) / (float)token_info->duration, @@ -196,4 +207,4 @@ void totp_generate_code_worker_set_lifetime_changed_handler( furi_check(context != NULL); context->on_code_lifetime_changed_handler = on_code_lifetime_changed_handler; context->on_code_lifetime_changed_handler_context = on_code_lifetime_changed_handler_context; -} \ No newline at end of file +} diff --git a/workers/generate_totp_code/generate_totp_code.h b/workers/generate_totp_code/generate_totp_code.h index eb234313e3a..8790f452008 100644 --- a/workers/generate_totp_code/generate_totp_code.h +++ b/workers/generate_totp_code/generate_totp_code.h @@ -27,7 +27,7 @@ enum TotGenerateCodeWorkerEvents { TotpGenerateCodeWorkerEventStop = 0b01, /** - * @brief Trigger token input automation + * @brief Triggers OTP code generation */ TotpGenerateCodeWorkerEventForceUpdate = 0b10 }; @@ -83,4 +83,4 @@ void totp_generate_code_worker_set_code_generated_handler( void totp_generate_code_worker_set_lifetime_changed_handler( TotpGenerateCodeWorkerContext* context, TOTP_CODE_LIFETIME_CHANGED_HANDLER on_code_lifetime_changed_handler, - void* on_code_lifetime_changed_handler_context); \ No newline at end of file + void* on_code_lifetime_changed_handler_context);