diff --git a/applications/plugins/totp/scenes/authenticate/totp_scene_authenticate.c b/applications/plugins/totp/scenes/authenticate/totp_scene_authenticate.c index 73cbb4aaa..4ed79accd 100644 --- a/applications/plugins/totp/scenes/authenticate/totp_scene_authenticate.c +++ b/applications/plugins/totp/scenes/authenticate/totp_scene_authenticate.c @@ -24,6 +24,7 @@ void totp_scene_authenticate_activate(PluginState* plugin_state) { scene_state->code_length = 0; memset(&scene_state->code_input[0], 0, MAX_CODE_LENGTH); plugin_state->current_scene_state = scene_state; + memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE); } void totp_scene_authenticate_render(Canvas* const canvas, PluginState* plugin_state) { diff --git a/applications/plugins/totp/scenes/generate_token/totp_scene_generate_token.c b/applications/plugins/totp/scenes/generate_token/totp_scene_generate_token.c index df5a3e746..674bcd41e 100644 --- a/applications/plugins/totp/scenes/generate_token/totp_scene_generate_token.c +++ b/applications/plugins/totp/scenes/generate_token/totp_scene_generate_token.c @@ -130,7 +130,7 @@ void totp_scene_generate_token_activate( } } SceneState* scene_state = malloc(sizeof(SceneState)); - if(context == NULL) { + if(context == NULL || context->current_token_index > plugin_state->tokens_count) { scene_state->current_token_index = 0; } else { scene_state->current_token_index = context->current_token_index; diff --git a/applications/plugins/totp/scenes/scene_director.c b/applications/plugins/totp/scenes/scene_director.c index 9a07b49fb..d4ddd1768 100644 --- a/applications/plugins/totp/scenes/scene_director.c +++ b/applications/plugins/totp/scenes/scene_director.c @@ -28,6 +28,8 @@ void totp_scene_director_activate_scene( case TotpSceneAppSettings: totp_scene_app_settings_activate(plugin_state, context); break; + case TotpSceneNone: + break; } plugin_state->current_scene = scene; @@ -51,6 +53,8 @@ void totp_scene_director_deactivate_active_scene(PluginState* const plugin_state case TotpSceneAppSettings: totp_scene_app_settings_deactivate(plugin_state); break; + case TotpSceneNone: + break; } } @@ -79,6 +83,8 @@ void totp_scene_director_render(Canvas* const canvas, PluginState* const plugin_ case TotpSceneAppSettings: totp_scene_app_settings_render(canvas, plugin_state); break; + case TotpSceneNone: + break; } } @@ -108,6 +114,8 @@ bool totp_scene_director_handle_event(PluginEvent* const event, PluginState* con case TotpSceneAppSettings: processing = totp_scene_app_settings_handle_event(event, plugin_state); break; + case TotpSceneNone: + break; } return processing; diff --git a/applications/plugins/totp/scenes/token_menu/totp_scene_token_menu.c b/applications/plugins/totp/scenes/token_menu/totp_scene_token_menu.c index 8e2356f86..22c37dc2c 100644 --- a/applications/plugins/totp/scenes/token_menu/totp_scene_token_menu.c +++ b/applications/plugins/totp/scenes/token_menu/totp_scene_token_menu.c @@ -139,13 +139,8 @@ bool totp_scene_token_menu_handle_event(PluginEvent* const event, PluginState* p dialog_message_show(plugin_state->dialogs, message); dialog_message_free(message); if(dialog_result == DialogMessageButtonRight) { - uint8_t i = 0; - - ListNode* list_node = plugin_state->tokens_list; - while(i < scene_state->current_token_index && list_node->next != NULL) { - list_node = list_node->next; - i++; - } + ListNode* list_node = list_element_at( + plugin_state->tokens_list, scene_state->current_token_index); TokenInfo* tokenInfo = list_node->data; token_info_free(tokenInfo); diff --git a/applications/plugins/totp/scenes/totp_scenes_enum.h b/applications/plugins/totp/scenes/totp_scenes_enum.h index 72bf4d76e..c2b153a02 100644 --- a/applications/plugins/totp/scenes/totp_scenes_enum.h +++ b/applications/plugins/totp/scenes/totp_scenes_enum.h @@ -1,6 +1,7 @@ #pragma once typedef enum { + TotpSceneNone, TotpSceneAuthentication, TotpSceneGenerateToken, TotpSceneAddNewToken, diff --git a/applications/plugins/totp/services/cli/cli.c b/applications/plugins/totp/services/cli/cli.c new file mode 100644 index 000000000..3a7afdd96 --- /dev/null +++ b/applications/plugins/totp/services/cli/cli.c @@ -0,0 +1,65 @@ +// Original idea: https://github.com/br0ziliy + +#include "cli.h" +#include +#include "cli_helpers.h" +#include "commands/list/list.h" +#include "commands/add/add.h" +#include "commands/delete/delete.h" +#include "commands/timezone/timezone.h" +#include "commands/help/help.h" + +static void totp_cli_print_unknown_command(FuriString* unknown_command) { + TOTP_CLI_PRINTF( + "Command \"%s\" is unknown. Use \"" TOTP_CLI_COMMAND_HELP + "\" command to get list of available commands.", + furi_string_get_cstr(unknown_command)); +} + +static void totp_cli_handler(Cli* cli, FuriString* args, void* context) { + PluginState* plugin_state = (PluginState*)context; + + FuriString* cmd = furi_string_alloc(); + + args_read_string_and_trim(args, cmd); + + if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_HELP) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_HELP_ALT) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_HELP_ALT2) == 0 || furi_string_empty(cmd)) { + totp_cli_command_help_handle(); + } else if( + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_ADD) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_ADD_ALT) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_ADD_ALT2) == 0) { + totp_cli_command_add_handle(plugin_state, args, cli); + } else if( + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_LIST) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_LIST_ALT) == 0) { + totp_cli_command_list_handle(plugin_state, cli); + } else if( + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_DELETE) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_DELETE_ALT) == 0) { + totp_cli_command_delete_handle(plugin_state, args, cli); + } else if( + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_TIMEZONE) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_TIMEZONE_ALT) == 0) { + totp_cli_command_timezone_handle(plugin_state, args, cli); + } else { + totp_cli_print_unknown_command(cmd); + } + + furi_string_free(cmd); +} + +void totp_cli_register_command_handler(PluginState* plugin_state) { + Cli* cli = furi_record_open(RECORD_CLI); + cli_add_command( + cli, TOTP_CLI_COMMAND_NAME, CliCommandFlagParallelSafe, totp_cli_handler, plugin_state); + furi_record_close(RECORD_CLI); +} + +void totp_cli_unregister_command_handler() { + Cli* cli = furi_record_open(RECORD_CLI); + cli_delete_command(cli, TOTP_CLI_COMMAND_NAME); + furi_record_close(RECORD_CLI); +} \ No newline at end of file diff --git a/applications/plugins/totp/services/cli/cli.h b/applications/plugins/totp/services/cli/cli.h new file mode 100644 index 000000000..5297dd61b --- /dev/null +++ b/applications/plugins/totp/services/cli/cli.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "../../types/plugin_state.h" + +void totp_cli_register_command_handler(PluginState* plugin_state); +void totp_cli_unregister_command_handler(); \ No newline at end of file diff --git a/applications/plugins/totp/services/cli/cli_helpers.c b/applications/plugins/totp/services/cli/cli_helpers.c new file mode 100644 index 000000000..81a1e4b77 --- /dev/null +++ b/applications/plugins/totp/services/cli/cli_helpers.c @@ -0,0 +1,22 @@ +#include "cli_helpers.h" +#include + +bool totp_cli_ensure_authenticated(PluginState* plugin_state, Cli* cli) { + if(plugin_state->current_scene == TotpSceneAuthentication) { + TOTP_CLI_PRINTF("Pleases enter PIN on your flipper device\r\n"); + + while(plugin_state->current_scene == TotpSceneAuthentication && + !cli_cmd_interrupt_received(cli)) { + furi_delay_ms(100); + } + + TOTP_CLI_DELETE_LAST_LINE(); + fflush(stdout); + + if(plugin_state->current_scene == TotpSceneAuthentication) { + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/applications/plugins/totp/services/cli/cli_helpers.h b/applications/plugins/totp/services/cli/cli_helpers.h new file mode 100644 index 000000000..216925e50 --- /dev/null +++ b/applications/plugins/totp/services/cli/cli_helpers.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include "../../types/plugin_state.h" + +#define TOTP_CLI_COMMAND_NAME "totp" + +#define DOCOPT_ARGUMENT(arg) "<" arg ">" +#define DOCOPT_OPTIONAL(param) "[" param "]" +#define DOCOPT_REQUIRED(param) "(" param ")" +#define DOCOPT_OPTION(option, value) option " " value +#define DOCOPT_SWITCH(option) option +#define DOCOPT_OPTIONS "[options]" +#define DOCOPT_DEFAULT(val) "[default: " val "]" + +#define TOTP_CLI_PRINTF(format, ...) \ + _Pragma(STRINGIFY(GCC diagnostic push)); \ + _Pragma(STRINGIFY(GCC diagnostic ignored "-Wdouble-promotion")); \ + printf(format, ##__VA_ARGS__); \ + _Pragma(STRINGIFY(GCC diagnostic pop)); + +#define TOTP_CLI_DELETE_LAST_LINE() TOTP_CLI_PRINTF("\033[A\33[2K\r") +#define TOTP_CLI_DELETE_CURRENT_LINE() TOTP_CLI_PRINTF("\33[2K\r") +#define TOTP_CLI_PRINT_INVALID_ARGUMENTS() \ + TOTP_CLI_PRINTF( \ + "Invalid command arguments. use \"help\" command to get list of available commands") + +bool totp_cli_ensure_authenticated(PluginState* plugin_state, Cli* cli); diff --git a/applications/plugins/totp/services/cli/commands/add/add.c b/applications/plugins/totp/services/cli/commands/add/add.c new file mode 100644 index 000000000..41ff5b860 --- /dev/null +++ b/applications/plugins/totp/services/cli/commands/add/add.c @@ -0,0 +1,218 @@ +#include "add.h" +#include +#include +#include "../../../list/list.h" +#include "../../../../types/token_info.h" +#include "../../../config/config.h" +#include "../../cli_helpers.h" +#include "../../../../scenes/scene_director.h" + +#define TOTP_CLI_COMMAND_ADD_ARG_NAME "name" +#define TOTP_CLI_COMMAND_ADD_ARG_ALGO "algo" +#define TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX "-a" +#define TOTP_CLI_COMMAND_ADD_ARG_DIGITS "digits" +#define TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX "-d" +#define TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX "-u" + +static bool token_info_set_digits_from_str(TokenInfo* token_info, FuriString* str) { + switch(furi_string_get_char(str, 0)) { + case '6': + token_info->digits = TOTP_6_DIGITS; + return true; + case '8': + token_info->digits = TOTP_8_DIGITS; + return true; + } + + return false; +} + +static bool token_info_set_algo_from_str(TokenInfo* token_info, FuriString* str) { + if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME) == 0) { + token_info->algo = SHA1; + return true; + } + + if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME) == 0) { + token_info->algo = SHA256; + return true; + } + + if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME) == 0) { + token_info->algo = SHA512; + return true; + } + + return false; +} + +void totp_cli_command_add_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_ADD ", " TOTP_CLI_COMMAND_ADD_ALT + ", " TOTP_CLI_COMMAND_ADD_ALT2 " Add new token\r\n"); +} + +void totp_cli_command_add_docopt_usage() { + TOTP_CLI_PRINTF( + " " TOTP_CLI_COMMAND_NAME + " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_ADD " | " TOTP_CLI_COMMAND_ADD_ALT " | " TOTP_CLI_COMMAND_ADD_ALT2) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_NAME) " " DOCOPT_OPTIONAL( + DOCOPT_OPTION( + TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX, + DOCOPT_ARGUMENT( + TOTP_CLI_COMMAND_ADD_ARG_ALGO))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX)) "\r\n"); +} + +void totp_cli_command_add_docopt_arguments() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_ADD_ARG_NAME " Token name\r\n"); +} + +void totp_cli_command_add_docopt_options() { + TOTP_CLI_PRINTF(" " DOCOPT_OPTION( + TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX, + DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_ALGO)) " Token hashing algorithm.\r\n"); + TOTP_CLI_PRINTF( + " Could be one of: sha1, sha256, sha512 " DOCOPT_DEFAULT("sha1") "\r\n"); + TOTP_CLI_PRINTF(" " DOCOPT_OPTION( + TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX, + DOCOPT_ARGUMENT( + TOTP_CLI_COMMAND_ADD_ARG_DIGITS)) " Number of digits to generate, one of: 6, 8 " DOCOPT_DEFAULT("6") "\r\n"); + TOTP_CLI_PRINTF(" " DOCOPT_SWITCH( + TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX) " Show console user input as-is without masking\r\n"); +} + +void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cli* cli) { + FuriString* temp_str = furi_string_alloc(); + const char* temp_cstr; + + TokenInfo* token_info = token_info_alloc(); + + // Reading token name + if(!args_read_probably_quoted_string_and_trim(args, temp_str)) { + TOTP_CLI_PRINT_INVALID_ARGUMENTS(); + furi_string_free(temp_str); + token_info_free(token_info); + return; + } + + temp_cstr = furi_string_get_cstr(temp_str); + token_info->name = malloc(strlen(temp_cstr) + 1); + strcpy(token_info->name, temp_cstr); + + // Read optional arguments + bool mask_user_input = true; + while(args_read_string_and_trim(args, temp_str)) { + bool parsed = false; + if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX) == 0) { + if(!args_read_string_and_trim(args, temp_str)) { + TOTP_CLI_PRINTF("Missed value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX + "\"\r\n"); + } else if(!token_info_set_algo_from_str(token_info, temp_str)) { + TOTP_CLI_PRINTF( + "\"%s\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX + "\"\r\n", + furi_string_get_cstr(temp_str)); + } else { + parsed = true; + } + } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX) == 0) { + if(!args_read_string_and_trim(args, temp_str)) { + TOTP_CLI_PRINTF( + "Missed value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX + "\"\r\n"); + } else if(!token_info_set_digits_from_str(token_info, temp_str)) { + TOTP_CLI_PRINTF( + "\"%s\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX + "\"\r\n", + furi_string_get_cstr(temp_str)); + } else { + parsed = true; + } + } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX) == 0) { + mask_user_input = false; + parsed = true; + } + + if(!parsed) { + TOTP_CLI_PRINT_INVALID_ARGUMENTS(); + furi_string_free(temp_str); + token_info_free(token_info); + return; + } + } + + // Reading token secret + furi_string_reset(temp_str); + TOTP_CLI_PRINTF("Enter token secret and confirm with [ENTER]\r\n"); + + uint8_t c; + while(cli_read(cli, &c, 1) == 1) { + if(c == CliSymbolAsciiEsc) { + uint8_t c2; + cli_read_timeout(cli, &c2, 1, 0); + cli_read_timeout(cli, &c2, 1, 0); + } else if(c == CliSymbolAsciiETX) { + TOTP_CLI_DELETE_CURRENT_LINE(); + TOTP_CLI_PRINTF("Cancelled by user"); + furi_string_free(temp_str); + token_info_free(token_info); + return; + } else if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + if(mask_user_input) { + putc('*', stdout); + } else { + putc(c, stdout); + } + fflush(stdout); + furi_string_push_back(temp_str, c); + } else if(c == CliSymbolAsciiBackspace || c == CliSymbolAsciiDel) { + size_t temp_str_size = furi_string_size(temp_str); + if(temp_str_size > 0) { + TOTP_CLI_PRINTF("\b \b"); + fflush(stdout); + furi_string_left(temp_str, temp_str_size - 1); + } + } else if(c == CliSymbolAsciiCR) { + cli_nl(); + break; + } + } + + temp_cstr = furi_string_get_cstr(temp_str); + + TOTP_CLI_DELETE_LAST_LINE(); + + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + furi_string_free(temp_str); + token_info_free(token_info); + return; + } + + if(!token_info_set_secret(token_info, temp_cstr, strlen(temp_cstr), plugin_state->iv)) { + TOTP_CLI_PRINTF("Token secret seems to be invalid and can not be parsed\r\n"); + furi_string_free(temp_str); + token_info_free(token_info); + return; + } + + furi_string_reset(temp_str); + furi_string_free(temp_str); + + bool load_generate_token_scene = false; + if(plugin_state->current_scene == TotpSceneGenerateToken) { + totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL); + load_generate_token_scene = true; + } + + if(plugin_state->tokens_list == NULL) { + plugin_state->tokens_list = list_init_head(token_info); + } else { + list_add(plugin_state->tokens_list, token_info); + } + plugin_state->tokens_count++; + totp_config_file_save_new_token(token_info); + + if(load_generate_token_scene) { + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } + + TOTP_CLI_PRINTF("Token \"%s\" has been successfully added\r\n", token_info->name); +} \ No newline at end of file diff --git a/applications/plugins/totp/services/cli/commands/add/add.h b/applications/plugins/totp/services/cli/commands/add/add.h new file mode 100644 index 000000000..7b1138e5b --- /dev/null +++ b/applications/plugins/totp/services/cli/commands/add/add.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "../../../../types/plugin_state.h" + +#define TOTP_CLI_COMMAND_ADD "add" +#define TOTP_CLI_COMMAND_ADD_ALT "mk" +#define TOTP_CLI_COMMAND_ADD_ALT2 "new" + +void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cli* cli); +void totp_cli_command_add_docopt_commands(); +void totp_cli_command_add_docopt_usage(); +void totp_cli_command_add_docopt_arguments(); +void totp_cli_command_add_docopt_options(); \ No newline at end of file diff --git a/applications/plugins/totp/services/cli/commands/delete/delete.c b/applications/plugins/totp/services/cli/commands/delete/delete.c new file mode 100644 index 000000000..bbeb6ec4d --- /dev/null +++ b/applications/plugins/totp/services/cli/commands/delete/delete.c @@ -0,0 +1,107 @@ +#include "delete.h" + +#include +#include +#include +#include "../../../list/list.h" +#include "../../../config/config.h" +#include "../../cli_helpers.h" +#include "../../../../scenes/scene_director.h" + +#define TOTP_CLI_COMMAND_DELETE_ARG_INDEX "index" +#define TOTP_CLI_COMMAND_DELETE_ARG_FORCE_SUFFIX "-f" + +void totp_cli_command_delete_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_DELETE ", " TOTP_CLI_COMMAND_DELETE_ALT + " Delete existing token\r\n"); +} + +void totp_cli_command_delete_docopt_usage() { + TOTP_CLI_PRINTF( + " " TOTP_CLI_COMMAND_NAME + " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_DELETE " | " TOTP_CLI_COMMAND_DELETE_ALT) " " DOCOPT_ARGUMENT( + TOTP_CLI_COMMAND_DELETE_ARG_INDEX) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_DELETE_ARG_FORCE_SUFFIX)) "\r\n"); +} + +void totp_cli_command_delete_docopt_arguments() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_DELETE_ARG_INDEX " Token index in the list\r\n"); +} + +void totp_cli_command_delete_docopt_options() { + TOTP_CLI_PRINTF(" " DOCOPT_SWITCH( + TOTP_CLI_COMMAND_DELETE_ARG_FORCE_SUFFIX) " Force command to do not ask user for interactive confirmation\r\n"); +} + +void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args, Cli* cli) { + int token_number; + if(!args_read_int_and_trim(args, &token_number) || token_number <= 0 || + token_number > plugin_state->tokens_count) { + TOTP_CLI_PRINT_INVALID_ARGUMENTS(); + return; + } + + FuriString* temp_str = furi_string_alloc(); + bool confirm_needed = true; + if(args_read_string_and_trim(args, temp_str)) { + if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_DELETE_ARG_FORCE_SUFFIX) == 0) { + confirm_needed = false; + } else { + TOTP_CLI_PRINTF("Unknown argument \"%s\"\r\n", furi_string_get_cstr(temp_str)); + TOTP_CLI_PRINT_INVALID_ARGUMENTS(); + furi_string_free(temp_str); + return; + } + } + furi_string_free(temp_str); + + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + + ListNode* list_node = list_element_at(plugin_state->tokens_list, token_number - 1); + + TokenInfo* token_info = list_node->data; + + bool confirmed = !confirm_needed; + if(confirm_needed) { + TOTP_CLI_PRINTF("WARNING!\r\n"); + TOTP_CLI_PRINTF( + "TOKEN \"%s\" WILL BE PERMANENTLY DELETED WITHOUT ABILITY TO RECOVER IT.\r\n", + token_info->name); + TOTP_CLI_PRINTF("Confirm? [y/n]\r\n"); + fflush(stdout); + char user_pick; + do { + user_pick = tolower(cli_getc(cli)); + } while(user_pick != 'y' && user_pick != 'n' && user_pick != CliSymbolAsciiCR && + user_pick != CliSymbolAsciiETX && user_pick != CliSymbolAsciiEsc); + + confirmed = user_pick == 'y' || user_pick == CliSymbolAsciiCR; + } + + if(confirmed) { + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + + bool activate_generate_token_scene = false; + if(plugin_state->current_scene != TotpSceneAuthentication) { + totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL); + activate_generate_token_scene = true; + } + + plugin_state->tokens_list = list_remove(plugin_state->tokens_list, list_node); + plugin_state->tokens_count--; + + totp_full_save_config_file(plugin_state); + + if(activate_generate_token_scene) { + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } + + TOTP_CLI_PRINTF("Token \"%s\" has been successfully deleted\r\n", token_info->name); + token_info_free(token_info); + } else { + TOTP_CLI_PRINTF("User not confirmed\r\n"); + } +} \ No newline at end of file diff --git a/applications/plugins/totp/services/cli/commands/delete/delete.h b/applications/plugins/totp/services/cli/commands/delete/delete.h new file mode 100644 index 000000000..0b60932e3 --- /dev/null +++ b/applications/plugins/totp/services/cli/commands/delete/delete.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include "../../../../types/plugin_state.h" + +#define TOTP_CLI_COMMAND_DELETE "delete" +#define TOTP_CLI_COMMAND_DELETE_ALT "rm" + +void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args, Cli* cli); +void totp_cli_command_delete_docopt_commands(); +void totp_cli_command_delete_docopt_usage(); +void totp_cli_command_delete_docopt_arguments(); +void totp_cli_command_delete_docopt_options(); \ No newline at end of file diff --git a/applications/plugins/totp/services/cli/commands/help/help.c b/applications/plugins/totp/services/cli/commands/help/help.c new file mode 100644 index 000000000..ab592fbba --- /dev/null +++ b/applications/plugins/totp/services/cli/commands/help/help.c @@ -0,0 +1,42 @@ +#include "help.h" +#include "../../cli_helpers.h" +#include "../add/add.h" +#include "../delete/delete.h" +#include "../list/list.h" +#include "../timezone/timezone.h" + +void totp_cli_command_help_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_HELP ", " TOTP_CLI_COMMAND_HELP_ALT + ", " TOTP_CLI_COMMAND_HELP_ALT2 " Show command usage help\r\n"); +} + +void totp_cli_command_help_docopt_usage() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_NAME " " DOCOPT_REQUIRED( + TOTP_CLI_COMMAND_HELP " | " TOTP_CLI_COMMAND_HELP_ALT + " | " TOTP_CLI_COMMAND_HELP_ALT2) "\r\n"); +} + +void totp_cli_command_help_handle() { + TOTP_CLI_PRINTF("Usage:\r\n"); + totp_cli_command_help_docopt_usage(); + totp_cli_command_list_docopt_usage(); + totp_cli_command_add_docopt_usage(); + totp_cli_command_delete_docopt_usage(); + totp_cli_command_timezone_docopt_usage(); + cli_nl(); + TOTP_CLI_PRINTF("Commands:\r\n"); + totp_cli_command_help_docopt_commands(); + totp_cli_command_list_docopt_commands(); + totp_cli_command_add_docopt_commands(); + totp_cli_command_delete_docopt_commands(); + totp_cli_command_timezone_docopt_commands(); + cli_nl(); + TOTP_CLI_PRINTF("Arguments:\r\n"); + totp_cli_command_add_docopt_arguments(); + totp_cli_command_delete_docopt_arguments(); + totp_cli_command_timezone_docopt_arguments(); + cli_nl(); + TOTP_CLI_PRINTF("Options:\r\n"); + totp_cli_command_add_docopt_options(); + totp_cli_command_delete_docopt_options(); +} \ No newline at end of file diff --git a/applications/plugins/totp/services/cli/commands/help/help.h b/applications/plugins/totp/services/cli/commands/help/help.h new file mode 100644 index 000000000..4268b8bc5 --- /dev/null +++ b/applications/plugins/totp/services/cli/commands/help/help.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#define TOTP_CLI_COMMAND_HELP "help" +#define TOTP_CLI_COMMAND_HELP_ALT "h" +#define TOTP_CLI_COMMAND_HELP_ALT2 "?" + +void totp_cli_command_help_handle(); +void totp_cli_command_help_docopt_commands(); +void totp_cli_command_help_docopt_usage(); \ No newline at end of file diff --git a/applications/plugins/totp/services/cli/commands/list/list.c b/applications/plugins/totp/services/cli/commands/list/list.c new file mode 100644 index 000000000..61791084d --- /dev/null +++ b/applications/plugins/totp/services/cli/commands/list/list.c @@ -0,0 +1,71 @@ +#include "list.h" +#include +#include "../../../list/list.h" +#include "../../../../types/token_info.h" +#include "../../../config/constants.h" +#include "../../cli_helpers.h" + +static char* get_algo_as_cstr(TokenHashAlgo algo) { + switch(algo) { + case SHA1: + return TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME; + case SHA256: + return TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME; + case SHA512: + return TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME; + } + + return "UNKNOWN"; +} + +static uint8_t get_digits_as_int(TokenDigitsCount digits) { + switch(digits) { + case TOTP_6_DIGITS: + return 6; + case TOTP_8_DIGITS: + return 8; + } + + return 6; +} + +void totp_cli_command_list_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_LIST ", " TOTP_CLI_COMMAND_LIST_ALT + " List all available tokens\r\n"); +} + +void totp_cli_command_list_docopt_usage() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_NAME " " DOCOPT_REQUIRED( + TOTP_CLI_COMMAND_LIST " | " TOTP_CLI_COMMAND_LIST_ALT) "\r\n"); +} + +void totp_cli_command_list_handle(PluginState* plugin_state, Cli* cli) { + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + + if(plugin_state->tokens_list == NULL) { + TOTP_CLI_PRINTF("There are no tokens"); + return; + } + + ListNode* node = plugin_state->tokens_list; + + TOTP_CLI_PRINTF("+-----+-----------------------------+--------+--------+\r\n"); + TOTP_CLI_PRINTF("| %-*s | %-*s | %-*s | %-s |\r\n", 3, "#", 27, "Name", 6, "Algo", "Digits"); + TOTP_CLI_PRINTF("+-----+-----------------------------+--------+--------+\r\n"); + uint16_t index = 1; + while(node != NULL) { + TokenInfo* token_info = (TokenInfo*)node->data; + token_info_get_digits_count(token_info); + TOTP_CLI_PRINTF( + "| %-3" PRIu16 " | %-27.27s | %-6s | %-6" PRIu8 " |\r\n", + index, + token_info->name, + get_algo_as_cstr(token_info->algo), + get_digits_as_int(token_info->digits)); + node = node->next; + index++; + } + TOTP_CLI_PRINTF("+-----+-----------------------------+--------+--------+\r\n"); +} \ No newline at end of file diff --git a/applications/plugins/totp/services/cli/commands/list/list.h b/applications/plugins/totp/services/cli/commands/list/list.h new file mode 100644 index 000000000..d8c3cb127 --- /dev/null +++ b/applications/plugins/totp/services/cli/commands/list/list.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include "../../../../types/plugin_state.h" + +#define TOTP_CLI_COMMAND_LIST "list" +#define TOTP_CLI_COMMAND_LIST_ALT "ls" + +void totp_cli_command_list_handle(PluginState* plugin_state, Cli* cli); +void totp_cli_command_list_docopt_commands(); +void totp_cli_command_list_docopt_usage(); diff --git a/applications/plugins/totp/services/cli/commands/timezone/timezone.c b/applications/plugins/totp/services/cli/commands/timezone/timezone.c new file mode 100644 index 000000000..d997aa116 --- /dev/null +++ b/applications/plugins/totp/services/cli/commands/timezone/timezone.c @@ -0,0 +1,54 @@ +#include "timezone.h" +#include +#include "../../../config/config.h" +#include "../../../../scenes/scene_director.h" +#include "../../cli_helpers.h" + +#define TOTP_CLI_COMMAND_TIMEZONE_ARG_TIMEZONE "timezone" + +void totp_cli_command_timezone_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_TIMEZONE ", " TOTP_CLI_COMMAND_TIMEZONE_ALT + " Get or set current timezone\r\n"); +} + +void totp_cli_command_timezone_docopt_usage() { + TOTP_CLI_PRINTF( + " " TOTP_CLI_COMMAND_NAME + " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_TIMEZONE " | " TOTP_CLI_COMMAND_TIMEZONE_ALT) " " DOCOPT_OPTIONAL( + DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_TIMEZONE_ARG_TIMEZONE)) "\r\n"); +} + +void totp_cli_command_timezone_docopt_arguments() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_TIMEZONE_ARG_TIMEZONE + " Timezone offset in hours to be set.\r\n"); + TOTP_CLI_PRINTF( + " If not provided then current timezone offset will be printed\r\n"); +} + +void totp_cli_command_timezone_handle(PluginState* plugin_state, FuriString* args, Cli* cli) { + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + + FuriString* temp_str = furi_string_alloc(); + if(args_read_string_and_trim(args, temp_str)) { + float tz = strtof(furi_string_get_cstr(temp_str), NULL); + if(tz >= -12.75f && tz <= 12.75f) { + plugin_state->timezone_offset = tz; + totp_config_file_update_timezone_offset(tz); + TOTP_CLI_PRINTF("Timezone is set to %f\r\n", tz); + if(plugin_state->current_scene == TotpSceneGenerateToken) { + totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL); + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } else if(plugin_state->current_scene == TotpSceneAppSettings) { + totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL); + totp_scene_director_activate_scene(plugin_state, TotpSceneAppSettings, NULL); + } + } else { + TOTP_CLI_PRINTF("Invalid timezone offset\r\n"); + } + } else { + TOTP_CLI_PRINTF("Current timezone offset is %f\r\n", plugin_state->timezone_offset); + } + furi_string_free(temp_str); +} \ No newline at end of file diff --git a/applications/plugins/totp/services/cli/commands/timezone/timezone.h b/applications/plugins/totp/services/cli/commands/timezone/timezone.h new file mode 100644 index 000000000..0c0d82abf --- /dev/null +++ b/applications/plugins/totp/services/cli/commands/timezone/timezone.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include "../../../../types/plugin_state.h" + +#define TOTP_CLI_COMMAND_TIMEZONE "timezone" +#define TOTP_CLI_COMMAND_TIMEZONE_ALT "tz" + +void totp_cli_command_timezone_handle(PluginState* plugin_state, FuriString* args, Cli* cli); +void totp_cli_command_timezone_docopt_commands(); +void totp_cli_command_timezone_docopt_usage(); +void totp_cli_command_timezone_docopt_arguments(); \ No newline at end of file diff --git a/applications/plugins/totp/services/config/config.c b/applications/plugins/totp/services/config/config.c index 9a72f9ca8..0854d79ad 100644 --- a/applications/plugins/totp/services/config/config.c +++ b/applications/plugins/totp/services/config/config.c @@ -10,7 +10,7 @@ #define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/totp.conf" #define CONFIG_FILE_BACKUP_PATH CONFIG_FILE_PATH ".backup" -uint8_t token_info_get_digits_as_int(TokenInfo* token_info) { +static uint8_t token_info_get_digits_as_int(TokenInfo* token_info) { switch(token_info->digits) { case TOTP_6_DIGITS: return 6; @@ -21,7 +21,7 @@ uint8_t token_info_get_digits_as_int(TokenInfo* token_info) { return 6; } -void token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits) { +static void token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits) { switch(digits) { case 6: token_info->digits = TOTP_6_DIGITS; @@ -32,7 +32,7 @@ void token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits) { } } -char* token_info_get_algo_as_cstr(TokenInfo* token_info) { +static char* token_info_get_algo_as_cstr(TokenInfo* token_info) { switch(token_info->algo) { case SHA1: return TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME; @@ -45,12 +45,12 @@ char* token_info_get_algo_as_cstr(TokenInfo* token_info) { return NULL; } -void token_info_set_algo_from_str(TokenInfo* token_info, FuriString* str) { +static void token_info_set_algo_from_str(TokenInfo* token_info, FuriString* str) { if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME) == 0) { token_info->algo = SHA1; - } else if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME)) { + } else if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME) == 0) { token_info->algo = SHA256; - } else if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME)) { + } else if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME) == 0) { token_info->algo = SHA512; } } diff --git a/applications/plugins/totp/services/list/list.c b/applications/plugins/totp/services/list/list.c index 77df1105a..3a3317980 100644 --- a/applications/plugins/totp/services/list/list.c +++ b/applications/plugins/totp/services/list/list.c @@ -44,6 +44,10 @@ ListNode* list_element_at(ListNode* head, uint16_t index) { } ListNode* list_remove(ListNode* head, ListNode* ep) { + if(head == NULL) { + return NULL; + } + if(head == ep) { ListNode* new_head = head->next; free(head); diff --git a/applications/plugins/totp/totp_app.c b/applications/plugins/totp/totp_app.c index 265597937..9cc47c0d4 100644 --- a/applications/plugins/totp/totp_app.c +++ b/applications/plugins/totp/totp_app.c @@ -18,12 +18,13 @@ #include "scenes/scene_director.h" #include "services/ui/constants.h" #include "services/crypto/crypto.h" +#include "services/cli/cli.h" #define IDLE_TIMEOUT 60000 static void render_callback(Canvas* const canvas, void* ctx) { PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25); - if(plugin_state != NULL && !plugin_state->changing_scene) { + if (plugin_state != NULL && !plugin_state->changing_scene) { totp_scene_director_render(canvas, plugin_state); } @@ -37,51 +38,40 @@ static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queu furi_message_queue_put(event_queue, &event, FuriWaitForever); } -static bool totp_state_init(PluginState* const plugin_state) { +static bool totp_plugin_state_init(PluginState* const plugin_state) { plugin_state->gui = furi_record_open(RECORD_GUI); plugin_state->notification = furi_record_open(RECORD_NOTIFICATION); plugin_state->dialogs = furi_record_open(RECORD_DIALOGS); + totp_config_file_load_base(plugin_state); + totp_cli_register_command_handler(plugin_state); + totp_scene_director_init_scenes(plugin_state); - if(plugin_state->crypto_verify_data == NULL) { + if (plugin_state->crypto_verify_data == NULL) { DialogMessage* message = dialog_message_alloc(); dialog_message_set_buttons(message, "No", NULL, "Yes"); - dialog_message_set_text( - message, - "Would you like to setup PIN?", - SCREEN_WIDTH_CENTER, - SCREEN_HEIGHT_CENTER, - AlignCenter, - AlignCenter); + dialog_message_set_text(message, "Would you like to setup PIN?", SCREEN_WIDTH_CENTER, SCREEN_HEIGHT_CENTER, AlignCenter, AlignCenter); DialogMessageButton dialog_result = dialog_message_show(plugin_state->dialogs, message); dialog_message_free(message); - if(dialog_result == DialogMessageButtonRight) { + if (dialog_result == DialogMessageButtonRight) { totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL); } else { totp_crypto_seed_iv(plugin_state, NULL, 0); totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); } - } else if(plugin_state->pin_set) { + } else if (plugin_state->pin_set) { totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL); } else { totp_crypto_seed_iv(plugin_state, NULL, 0); - if(totp_crypto_verify_key(plugin_state)) { + if (totp_crypto_verify_key(plugin_state)) { totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); } else { - FURI_LOG_E( - LOGGING_TAG, - "Digital signature verification failed. Looks like conf file was created on another flipper and can't be used on any other"); + FURI_LOG_E(LOGGING_TAG, "Digital signature verification failed. Looks like conf file was created on another flipper and can't be used on any other"); DialogMessage* message = dialog_message_alloc(); dialog_message_set_buttons(message, "Exit", NULL, NULL); - dialog_message_set_text( - message, - "Digital signature verification failed", - SCREEN_WIDTH_CENTER, - SCREEN_HEIGHT_CENTER, - AlignCenter, - AlignCenter); + dialog_message_set_text(message, "Digital signature verification failed", SCREEN_WIDTH_CENTER, SCREEN_HEIGHT_CENTER, AlignCenter, AlignCenter); dialog_message_show(plugin_state->dialogs, message); dialog_message_free(message); return false; @@ -91,7 +81,9 @@ static bool totp_state_init(PluginState* const plugin_state) { return true; } -static void plugin_state_free(PluginState* plugin_state) { +static void totp_plugin_state_free(PluginState* plugin_state) { + totp_cli_unregister_command_handler(); + totp_scene_director_deactivate_active_scene(plugin_state); totp_scene_director_dispose(plugin_state); @@ -102,7 +94,7 @@ static void plugin_state_free(PluginState* plugin_state) { ListNode* node = plugin_state->tokens_list; ListNode* tmp; - while(node != NULL) { + while (node != NULL) { tmp = node->next; TokenInfo* tokenInfo = node->data; token_info_free(tokenInfo); @@ -110,7 +102,7 @@ static void plugin_state_free(PluginState* plugin_state) { node = tmp; } - if(plugin_state->crypto_verify_data != NULL) { + if (plugin_state->crypto_verify_data != NULL) { free(plugin_state->crypto_verify_data); } free(plugin_state); @@ -120,16 +112,16 @@ int32_t totp_app() { FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); PluginState* plugin_state = malloc(sizeof(PluginState)); - if(!totp_state_init(plugin_state)) { + if (!totp_plugin_state_init(plugin_state)) { FURI_LOG_E(LOGGING_TAG, "App state initialization failed\r\n"); - plugin_state_free(plugin_state); + totp_plugin_state_free(plugin_state); return 254; } ValueMutex state_mutex; if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) { FURI_LOG_E(LOGGING_TAG, "Cannot create mutex\r\n"); - plugin_state_free(plugin_state); + totp_plugin_state_free(plugin_state); return 255; } @@ -145,20 +137,18 @@ int32_t totp_app() { bool processing = true; uint32_t last_user_interaction_time = furi_get_tick(); while(processing) { - if(plugin_state->changing_scene) continue; + if (plugin_state->changing_scene) continue; FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); PluginState* plugin_state = acquire_mutex_block(&state_mutex); if(event_status == FuriStatusOk) { - if(event.type == EventTypeKey) { + if (event.type == EventTypeKey) { last_user_interaction_time = furi_get_tick(); } processing = totp_scene_director_handle_event(&event, plugin_state); - } else if( - plugin_state->pin_set && plugin_state->current_scene != TotpSceneAuthentication && - furi_get_tick() - last_user_interaction_time > IDLE_TIMEOUT) { + } else if (plugin_state->pin_set && plugin_state->current_scene != TotpSceneAuthentication && furi_get_tick() - last_user_interaction_time > IDLE_TIMEOUT) { totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL); } @@ -171,6 +161,6 @@ int32_t totp_app() { view_port_free(view_port); furi_message_queue_free(event_queue); delete_mutex(&state_mutex); - plugin_state_free(plugin_state); + totp_plugin_state_free(plugin_state); return 0; }