Reorg, added more sources

This commit is contained in:
UberGuidoZ
2022-12-28 22:34:27 -08:00
parent 034d4cd172
commit 24f88bf731
1646 changed files with 39096 additions and 0 deletions
@@ -0,0 +1,191 @@
---
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: AlwaysBreak
AlignArrayOfStructures: None
AlignConsecutiveMacros: None
AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: None
AlignConsecutiveDeclarations: None
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments: false
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortEnumsOnASingleLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
AttributeMacros:
- __capability
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: true
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: false
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeComma
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: false
ColumnLimit: 99
CommentPragmas: '^ IWYU pragma:'
QualifierAlignment: Leave
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
PackConstructorInitializers: BinPack
BasedOnStyle: ''
ConstructorInitializerAllOnOneLineOrOnePerLine: false
AllowAllConstructorInitializersOnNextLine: true
FixNamespaceComments: false
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseLabels: false
IndentCaseBlocks: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: AfterExternBlock
IndentRequires: false
IndentWidth: 4
IndentWrappedFunctionNames: true
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
LambdaBodyIndentation: Signature
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 4
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 10
PenaltyBreakBeforeFirstCallParameter: 30
PenaltyBreakComment: 10
PenaltyBreakFirstLessLess: 0
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 10
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 100
PenaltyReturnTypeOnItsOwnLine: 60
PenaltyIndentedWhitespace: 0
PointerAlignment: Left
PPIndentWidth: -1
ReferenceAlignment: Pointer
ReflowComments: false
RemoveBracesLLVM: false
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SortIncludes: Never
SortJavaStaticImport: Before
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: Never
SpaceBeforeParensOptions:
AfterControlStatements: false
AfterForeachMacros: false
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: false
AfterOverloadedOperator: false
BeforeNonEmptyParentheses: false
SpaceAroundPointerQualifiers: Default
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInConditionalStatement: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
BitFieldColonSpacing: Both
Standard: c++03
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 4
UseCRLF: false
UseTab: Never
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
- NS_SWIFT_NAME
- CF_SWIFT_NAME
...
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Alexander Kopachov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,37 @@
App(
appid="totp",
name="Authenticator",
apptype=FlipperAppType.EXTERNAL,
entry_point="totp_app",
cdefines=["APP_TOTP"],
requires=[
"gui",
"cli",
"dialogs",
"storage",
"input",
"notification"
],
stack_size=2 * 1024,
order=20,
fap_category="Misc",
fap_icon_assets="images",
fap_icon="totp_10px.png",
fap_private_libs=[
Lib(
name="base32",
),
Lib(
name="list",
),
Lib(
name="timezone_utils",
),
Lib(
name="polyfills",
),
Lib(
name="roll_value",
),
],
)
@@ -0,0 +1,76 @@
// Original idea: https://github.com/br0ziliy
#include "cli.h"
#include <lib/toolbox/args.h>
#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"
#include "commands/move/move.h"
#include "commands/pin/pin.h"
#include "commands/notification/notification.h"
static void totp_cli_print_unknown_command(const 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 if(
furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_MOVE) == 0 ||
furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_MOVE_ALT) == 0) {
totp_cli_command_move_handle(plugin_state, args, cli);
} else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_PIN) == 0) {
totp_cli_command_pin_handle(plugin_state, args, cli);
} else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_NOTIFICATION) == 0) {
totp_cli_command_notification_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);
}
@@ -0,0 +1,7 @@
#pragma once
#include <cli/cli.h>
#include "../types/plugin_state.h"
void totp_cli_register_command_handler(PluginState* plugin_state);
void totp_cli_unregister_command_handler();
@@ -0,0 +1,21 @@
#include "cli_helpers.h"
#include <cli/cli.h>
bool totp_cli_ensure_authenticated(const 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();
if(plugin_state->current_scene == TotpSceneAuthentication) { //-V547
return false;
}
}
return true;
}
@@ -0,0 +1,51 @@
#pragma once
#include <cli/cli.h>
#include "../types/plugin_state.h"
#define TOTP_CLI_COMMAND_NAME "totp"
#define DOCOPT_ARGUMENT(arg) "<" arg ">"
#define DOCOPT_MULTIPLE(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, ...) \
do { \
_Pragma(STRINGIFY(GCC diagnostic push)) \
_Pragma(STRINGIFY(GCC diagnostic ignored "-Wdouble-promotion")) \
printf(format, ##__VA_ARGS__); \
_Pragma(STRINGIFY(GCC diagnostic pop)) \
} while(false)
#define TOTP_CLI_DELETE_LAST_LINE() \
TOTP_CLI_PRINTF("\033[A\33[2K\r"); \
fflush(stdout)
#define TOTP_CLI_DELETE_CURRENT_LINE() \
TOTP_CLI_PRINTF("\33[2K\r"); \
fflush(stdout)
#define TOTP_CLI_DELETE_LAST_CHAR() \
TOTP_CLI_PRINTF("\b \b"); \
fflush(stdout)
#define TOTP_CLI_PRINT_INVALID_ARGUMENTS() \
TOTP_CLI_PRINTF( \
"Invalid command arguments. use \"help\" command to get list of available commands")
#define TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE() \
TOTP_CLI_PRINTF("An error has occurred during updating config file\r\n")
/**
* @brief Checks whether user is authenticated and entered correct PIN.
* If user is not authenticated it prompts user to enter correct PIN to authenticate.
* @param plugin_state application state
* @param cli reference to the firmware CLI subsystem
* @return \c true if user is already authenticated or successfully authenticated; \c false otherwise
*/
bool totp_cli_ensure_authenticated(const PluginState* plugin_state, Cli* cli);
@@ -0,0 +1,218 @@
#include "add.h"
#include <stdlib.h>
#include <lib/toolbox/args.h>
#include "../../../lib/list/list.h"
#include "../../../types/token_info.h"
#include "../../../services/config/config.h"
#include "../../../services/convert/convert.h"
#include "../../cli_helpers.h"
#include "../../../ui/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_algo_from_str(TokenInfo* token_info, const 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");
cli_nl();
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");
}
static void furi_string_secure_free(FuriString* str) {
for(long i = furi_string_size(str) - 1; i >= 0; i--) {
furi_string_set_char(str, i, '\0');
}
furi_string_free(str);
}
static bool totp_cli_read_secret(Cli* cli, FuriString* out_str, bool mask_user_input) {
uint8_t c;
while(cli_read(cli, &c, 1) == 1) {
if(c == CliSymbolAsciiEsc) {
// Some keys generating escape-sequences
// We need to ignore them as we care about alpha-numerics only
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\r\n");
return false;
} 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(out_str, c);
} else if(c == CliSymbolAsciiBackspace || c == CliSymbolAsciiDel) {
size_t out_str_size = furi_string_size(out_str);
if(out_str_size > 0) {
TOTP_CLI_DELETE_LAST_CHAR();
furi_string_left(out_str, out_str_size - 1);
}
} else if(c == CliSymbolAsciiCR) {
cli_nl();
break;
}
}
TOTP_CLI_DELETE_LAST_LINE();
return true;
}
void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
FuriString* temp_str = furi_string_alloc();
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;
}
size_t temp_cstr_len = furi_string_size(temp_str);
token_info->name = malloc(temp_cstr_len + 1);
furi_check(token_info->name != NULL);
strlcpy(token_info->name, furi_string_get_cstr(temp_str), temp_cstr_len + 1);
// 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_int(
token_info, CONVERT_CHAR_TO_DIGIT(furi_string_get_char(temp_str, 0)))) {
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;
} else {
TOTP_CLI_PRINTF("Unknown argument \"%s\"\r\n", furi_string_get_cstr(temp_str));
}
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");
if(!totp_cli_read_secret(cli, temp_str, mask_user_input) ||
!totp_cli_ensure_authenticated(plugin_state, cli)) {
furi_string_secure_free(temp_str);
token_info_free(token_info);
return;
}
if(!token_info_set_secret(
token_info,
furi_string_get_cstr(temp_str),
furi_string_size(temp_str),
plugin_state->iv)) {
TOTP_CLI_PRINTF("Token secret seems to be invalid and can not be parsed\r\n");
furi_string_secure_free(temp_str);
token_info_free(token_info);
return;
}
furi_string_secure_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;
}
TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, token_info, furi_check);
plugin_state->tokens_count++;
if(totp_config_file_save_new_token(token_info) == TotpConfigFileUpdateSuccess) {
TOTP_CLI_PRINTF("Token \"%s\" has been successfully added\r\n", token_info->name);
} else {
TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
}
if(load_generate_token_scene) {
totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
}
}
@@ -0,0 +1,14 @@
#pragma once
#include <cli/cli.h>
#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();
@@ -0,0 +1,110 @@
#include "delete.h"
#include <stdlib.h>
#include <ctype.h>
#include <lib/toolbox/args.h>
#include "../../../lib/list/list.h"
#include "../../../services/config/config.h"
#include "../../cli_helpers.h"
#include "../../../ui/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--;
if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) {
TOTP_CLI_PRINTF("Token \"%s\" has been successfully deleted\r\n", token_info->name);
} else {
TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
}
token_info_free(token_info);
if(activate_generate_token_scene) {
totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
}
} else {
TOTP_CLI_PRINTF("User not confirmed\r\n");
}
}
@@ -0,0 +1,13 @@
#pragma once
#include <cli/cli.h>
#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();
@@ -0,0 +1,53 @@
#include "help.h"
#include "../../cli_helpers.h"
#include "../add/add.h"
#include "../delete/delete.h"
#include "../list/list.h"
#include "../timezone/timezone.h"
#include "../move/move.h"
#include "../pin/pin.h"
#include "../notification/notification.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();
totp_cli_command_move_docopt_usage();
totp_cli_command_pin_docopt_usage();
totp_cli_command_notification_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();
totp_cli_command_move_docopt_commands();
totp_cli_command_pin_docopt_commands();
totp_cli_command_notification_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();
totp_cli_command_notification_docopt_arguments();
cli_nl();
TOTP_CLI_PRINTF("Options:\r\n");
totp_cli_command_add_docopt_options();
totp_cli_command_delete_docopt_options();
totp_cli_command_move_docopt_options();
}
@@ -0,0 +1,11 @@
#pragma once
#include <cli/cli.h>
#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();
@@ -0,0 +1,58 @@
#include "list.h"
#include <stdlib.h>
#include "../../../lib/list/list.h"
#include "../../../types/token_info.h"
#include "../../../services/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;
default:
break;
}
return "UNKNOWN";
}
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;
}
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;
TOTP_LIST_FOREACH(plugin_state->tokens_list, node, {
TokenInfo* token_info = (TokenInfo*)node->data;
TOTP_CLI_PRINTF(
"| %-3" PRIu16 " | %-27.27s | %-6s | %-6" PRIu8 " |\r\n",
index,
token_info->name,
get_algo_as_cstr(token_info->algo),
token_info->digits);
index++;
});
TOTP_CLI_PRINTF("+-----+-----------------------------+--------+--------+\r\n");
}
@@ -0,0 +1,11 @@
#pragma once
#include <cli/cli.h>
#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();
@@ -0,0 +1,164 @@
#include "move.h"
#include <stdlib.h>
#include <lib/toolbox/args.h>
#include "../../../lib/list/list.h"
#include "../../../types/token_info.h"
#include "../../../services/config/config.h"
#include "../../cli_helpers.h"
#include "../../../ui/scene_director.h"
#define TOTP_CLI_COMMAND_MOVE_ARG_INDEX "index"
#define TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME "name"
#define TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX "-n"
#define TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX "index"
#define TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX "-i"
void totp_cli_command_move_docopt_commands() {
TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_MOVE ", " TOTP_CLI_COMMAND_MOVE_ALT
" Move\\rename token\r\n");
}
void totp_cli_command_move_docopt_usage() {
TOTP_CLI_PRINTF(
" " TOTP_CLI_COMMAND_NAME
" " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_MOVE " | " TOTP_CLI_COMMAND_MOVE_ALT) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_INDEX) " " DOCOPT_OPTIONAL(
DOCOPT_OPTION(
TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX,
DOCOPT_ARGUMENT(
TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX))) "\r\n");
}
void totp_cli_command_move_docopt_options() {
TOTP_CLI_PRINTF(" " DOCOPT_OPTION(
TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX,
DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME)) " New token name.\r\n");
TOTP_CLI_PRINTF(" " DOCOPT_OPTION(
TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX,
DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX)) " New token index.\r\n");
}
void totp_cli_command_move_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
int token_index;
if(!args_read_int_and_trim(args, &token_index)) {
TOTP_CLI_PRINT_INVALID_ARGUMENTS();
return;
}
if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
return;
}
if(token_index < 1 || token_index > plugin_state->tokens_count) {
TOTP_CLI_PRINT_INVALID_ARGUMENTS();
return;
}
FuriString* temp_str = furi_string_alloc();
char* new_token_name = NULL;
int new_token_index = 0;
while(args_read_string_and_trim(args, temp_str)) {
bool parsed = false;
if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX) == 0) {
if(!args_read_string_and_trim(args, temp_str)) {
TOTP_CLI_PRINTF(
"Missed value for argument \"" TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX
"\"\r\n");
} else {
if(new_token_name != NULL) {
free(new_token_name);
}
new_token_name = malloc(furi_string_size(temp_str) + 1);
if(new_token_name == NULL) {
furi_string_free(temp_str);
return;
}
strlcpy(
new_token_name,
furi_string_get_cstr(temp_str),
furi_string_size(temp_str) + 1);
parsed = true;
}
} else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX) == 0) {
if(!args_read_int_and_trim(args, &new_token_index)) {
TOTP_CLI_PRINTF(
"Missed value for argument \"" TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX
"\"\r\n");
} else if(new_token_index < 1 || new_token_index > plugin_state->tokens_count) {
TOTP_CLI_PRINTF(
"\"%" PRId16
"\" is incorrect value for argument \"" TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX
"\"\r\n",
new_token_index);
} else {
parsed = true;
}
} else {
TOTP_CLI_PRINTF("Unknown argument \"%s\"\r\n", furi_string_get_cstr(temp_str));
}
if(!parsed) {
TOTP_CLI_PRINT_INVALID_ARGUMENTS();
furi_string_free(temp_str);
if(new_token_name != NULL) {
free(new_token_name);
}
return;
}
}
if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
furi_string_free(temp_str);
if(new_token_name != NULL) {
free(new_token_name);
}
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;
}
bool token_updated = false;
TokenInfo* token_info = NULL;
if(new_token_index > 0) {
plugin_state->tokens_list =
list_remove_at(plugin_state->tokens_list, token_index - 1, (void**)&token_info);
furi_check(token_info != NULL);
plugin_state->tokens_list =
list_insert_at(plugin_state->tokens_list, new_token_index - 1, token_info);
token_updated = true;
} else {
token_info = list_element_at(plugin_state->tokens_list, token_index - 1)->data;
}
if(new_token_name != NULL) {
free(token_info->name);
token_info->name = new_token_name;
token_updated = true;
}
if(token_updated) {
if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) {
TOTP_CLI_PRINTF("Token \"%s\" has been successfully updated\r\n", token_info->name);
} else {
TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
}
} else {
TOTP_CLI_PRINT_INVALID_ARGUMENTS();
}
if(activate_generate_token_scene) {
totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
}
furi_string_free(temp_str);
}
@@ -0,0 +1,12 @@
#pragma once
#include <cli/cli.h>
#include "../../../types/plugin_state.h"
#define TOTP_CLI_COMMAND_MOVE "move"
#define TOTP_CLI_COMMAND_MOVE_ALT "mv"
void totp_cli_command_move_handle(PluginState* plugin_state, FuriString* args, Cli* cli);
void totp_cli_command_move_docopt_commands();
void totp_cli_command_move_docopt_usage();
void totp_cli_command_move_docopt_options();
@@ -0,0 +1,109 @@
#include "notification.h"
#include <lib/toolbox/args.h>
#include "../../../services/config/config.h"
#include "../../../ui/scene_director.h"
#include "../../cli_helpers.h"
#define TOTP_CLI_COMMAND_NOTIFICATION_ARG_METHOD "method"
#define TOTP_CLI_COMMAND_NOTIFICATION_METHOD_NONE "none"
#define TOTP_CLI_COMMAND_NOTIFICATION_METHOD_SOUND "sound"
#define TOTP_CLI_COMMAND_NOTIFICATION_METHOD_VIBRO "vibro"
void totp_cli_command_notification_docopt_commands() {
TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_NOTIFICATION
" Get or set notification method\r\n");
}
void totp_cli_command_notification_docopt_usage() {
TOTP_CLI_PRINTF(
" " TOTP_CLI_COMMAND_NAME " " TOTP_CLI_COMMAND_NOTIFICATION " " DOCOPT_OPTIONAL(
DOCOPT_MULTIPLE(DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_NOTIFICATION_ARG_METHOD))) "\r\n");
}
void totp_cli_command_notification_docopt_arguments() {
TOTP_CLI_PRINTF(
" " TOTP_CLI_COMMAND_NOTIFICATION_ARG_METHOD
" Notification method to be set. Must be one of [" TOTP_CLI_COMMAND_NOTIFICATION_METHOD_NONE
", " TOTP_CLI_COMMAND_NOTIFICATION_METHOD_SOUND
", " TOTP_CLI_COMMAND_NOTIFICATION_METHOD_VIBRO "]\r\n");
}
static void totp_cli_command_notification_print_method(NotificationMethod method) {
bool has_previous_method = false;
if(method & NotificationMethodSound) {
TOTP_CLI_PRINTF("\"" TOTP_CLI_COMMAND_NOTIFICATION_METHOD_SOUND "\"");
has_previous_method = true;
}
if(method & NotificationMethodVibro) {
if(has_previous_method) {
TOTP_CLI_PRINTF(" and ");
}
TOTP_CLI_PRINTF("\"" TOTP_CLI_COMMAND_NOTIFICATION_METHOD_VIBRO "\"");
}
if(method == NotificationMethodNone) {
TOTP_CLI_PRINTF("\"" TOTP_CLI_COMMAND_NOTIFICATION_METHOD_NONE "\"");
}
}
void totp_cli_command_notification_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
return;
}
FuriString* temp_str = furi_string_alloc();
bool new_method_provided = false;
NotificationMethod new_method = NotificationMethodNone;
bool args_valid = true;
while(args_read_string_and_trim(args, temp_str)) {
if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_NOTIFICATION_METHOD_NONE) == 0) {
new_method_provided = true;
new_method = NotificationMethodNone;
} else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_NOTIFICATION_METHOD_SOUND) == 0) {
new_method_provided = true;
new_method |= NotificationMethodSound;
} else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_NOTIFICATION_METHOD_VIBRO) == 0) {
new_method_provided = true;
new_method |= NotificationMethodVibro;
} else {
args_valid = false;
break;
}
}
do {
if(!args_valid) {
TOTP_CLI_PRINT_INVALID_ARGUMENTS();
break;
}
if(new_method_provided) {
Scene previous_scene = TotpSceneNone;
if(plugin_state->current_scene == TotpSceneGenerateToken ||
plugin_state->current_scene == TotpSceneAppSettings) {
previous_scene = plugin_state->current_scene;
totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
}
plugin_state->notification_method = new_method;
if(totp_config_file_update_notification_method(new_method) ==
TotpConfigFileUpdateSuccess) {
TOTP_CLI_PRINTF("Notification method is set to ");
totp_cli_command_notification_print_method(new_method);
cli_nl();
} else {
TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
}
if(previous_scene != TotpSceneNone) {
totp_scene_director_activate_scene(plugin_state, previous_scene, NULL);
}
} else {
TOTP_CLI_PRINTF("Current notification method is ");
totp_cli_command_notification_print_method(plugin_state->notification_method);
cli_nl();
}
} while(false);
furi_string_free(temp_str);
}
@@ -0,0 +1,11 @@
#pragma once
#include <cli/cli.h>
#include "../../../types/plugin_state.h"
#define TOTP_CLI_COMMAND_NOTIFICATION "notify"
void totp_cli_command_notification_handle(PluginState* plugin_state, FuriString* args, Cli* cli);
void totp_cli_command_notification_docopt_commands();
void totp_cli_command_notification_docopt_usage();
void totp_cli_command_notification_docopt_arguments();
@@ -0,0 +1,181 @@
#include "pin.h"
#include <stdlib.h>
#include <lib/toolbox/args.h>
#include "../../../types/token_info.h"
#include "../../../types/user_pin_codes.h"
#include "../../../services/config/config.h"
#include "../../cli_helpers.h"
#include "../../../lib/polyfills/memset_s.h"
#include "../../../services/crypto/crypto.h"
#include "../../../ui/scene_director.h"
#define TOTP_CLI_COMMAND_PIN_COMMAND_SET "set"
#define TOTP_CLI_COMMAND_PIN_COMMAND_REMOVE "remove"
void totp_cli_command_pin_docopt_commands() {
TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_PIN " Set\\change\\remove PIN\r\n");
}
void totp_cli_command_pin_docopt_usage() {
TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_NAME " " TOTP_CLI_COMMAND_PIN " " DOCOPT_REQUIRED(
TOTP_CLI_COMMAND_PIN_COMMAND_SET " | " TOTP_CLI_COMMAND_PIN_COMMAND_REMOVE) "\r\n");
}
static inline uint8_t totp_cli_key_to_pin_code(uint8_t key) {
uint8_t code = 0;
switch(key) {
case 0x44: // left
code = PinCodeArrowLeft;
break;
case 0x41: // up
code = PinCodeArrowUp;
break;
case 0x43: // right
code = PinCodeArrowRight;
break;
case 0x42: // down
code = PinCodeArrowDown;
break;
default:
break;
}
return code;
}
static bool totp_cli_read_pin(Cli* cli, uint8_t* pin, uint8_t* pin_length) {
TOTP_CLI_PRINTF("Enter new PIN (use arrow keys on your keyboard): ");
fflush(stdout);
uint8_t c;
*pin_length = 0;
while(cli_read(cli, &c, 1) == 1) {
if(c == CliSymbolAsciiEsc) {
uint8_t c2;
uint8_t c3;
if(cli_read_timeout(cli, &c2, 1, 0) == 1 && cli_read_timeout(cli, &c3, 1, 0) == 1 &&
c2 == 0x5b) {
uint8_t code = totp_cli_key_to_pin_code(c3);
if(code > 0) {
pin[*pin_length] = code;
*pin_length = *pin_length + 1;
putc('*', stdout);
fflush(stdout);
}
}
} else if(c == CliSymbolAsciiETX) {
TOTP_CLI_DELETE_CURRENT_LINE();
TOTP_CLI_PRINTF("Cancelled by user\r\n");
return false;
} else if(c == CliSymbolAsciiBackspace || c == CliSymbolAsciiDel) {
if(*pin_length > 0) {
*pin_length = *pin_length - 1;
pin[*pin_length] = 0;
TOTP_CLI_DELETE_LAST_CHAR();
}
} else if(c == CliSymbolAsciiCR) {
cli_nl();
break;
}
}
TOTP_CLI_DELETE_LAST_LINE();
return true;
}
void totp_cli_command_pin_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
UNUSED(plugin_state);
FuriString* temp_str = furi_string_alloc();
bool do_change = false;
bool do_remove = false;
UNUSED(do_remove);
if(args_read_string_and_trim(args, temp_str)) {
if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_PIN_COMMAND_SET) == 0) {
do_change = true;
} else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_PIN_COMMAND_REMOVE) == 0) {
do_remove = true;
} else {
TOTP_CLI_PRINT_INVALID_ARGUMENTS();
}
} else {
TOTP_CLI_PRINT_INVALID_ARGUMENTS();
}
if((do_change || do_remove) && totp_cli_ensure_authenticated(plugin_state, cli)) {
bool load_generate_token_scene = false;
do {
uint8_t old_iv[TOTP_IV_SIZE];
memcpy(&old_iv[0], &plugin_state->iv[0], TOTP_IV_SIZE);
uint8_t new_pin[TOTP_IV_SIZE];
uint8_t new_pin_length = 0;
if(do_change) {
if(!totp_cli_read_pin(cli, &new_pin[0], &new_pin_length) ||
!totp_cli_ensure_authenticated(plugin_state, cli)) {
memset_s(&new_pin[0], TOTP_IV_SIZE, 0, TOTP_IV_SIZE);
break;
}
} else if(do_remove) {
new_pin_length = 0;
memset(&new_pin[0], 0, TOTP_IV_SIZE);
}
if(plugin_state->current_scene == TotpSceneGenerateToken) {
totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
load_generate_token_scene = true;
}
TOTP_CLI_PRINTF("Encrypting, please wait...\r\n");
memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE);
memset(&plugin_state->base_iv[0], 0, TOTP_IV_SIZE);
if(plugin_state->crypto_verify_data != NULL) {
free(plugin_state->crypto_verify_data);
plugin_state->crypto_verify_data = NULL;
}
if(!totp_crypto_seed_iv(
plugin_state, new_pin_length > 0 ? &new_pin[0] : NULL, new_pin_length)) {
memset_s(&new_pin[0], TOTP_IV_SIZE, 0, TOTP_IV_SIZE);
TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
break;
}
memset_s(&new_pin[0], TOTP_IV_SIZE, 0, TOTP_IV_SIZE);
TOTP_LIST_FOREACH(plugin_state->tokens_list, node, {
TokenInfo* token_info = node->data;
size_t plain_token_length;
uint8_t* plain_token = totp_crypto_decrypt(
token_info->token, token_info->token_length, &old_iv[0], &plain_token_length);
free(token_info->token);
token_info->token = totp_crypto_encrypt(
plain_token,
plain_token_length,
&plugin_state->iv[0],
&token_info->token_length);
memset_s(plain_token, plain_token_length, 0, plain_token_length);
free(plain_token);
});
TOTP_CLI_DELETE_LAST_LINE();
if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) {
if(do_change) {
TOTP_CLI_PRINTF("PIN has been successfully changed\r\n");
} else if(do_remove) {
TOTP_CLI_PRINTF("PIN has been successfully removed\r\n");
}
} else {
TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
}
} while(false);
if(load_generate_token_scene) {
totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
}
}
furi_string_free(temp_str);
}
@@ -0,0 +1,10 @@
#pragma once
#include <cli/cli.h>
#include "../../../types/plugin_state.h"
#define TOTP_CLI_COMMAND_PIN "pin"
void totp_cli_command_pin_handle(PluginState* plugin_state, FuriString* args, Cli* cli);
void totp_cli_command_pin_docopt_commands();
void totp_cli_command_pin_docopt_usage();
@@ -0,0 +1,55 @@
#include "timezone.h"
#include <lib/toolbox/args.h>
#include "../../../services/config/config.h"
#include "../../../ui/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");
}
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;
if(totp_config_file_update_timezone_offset(tz) == TotpConfigFileUpdateSuccess) {
TOTP_CLI_PRINTF("Timezone is set to %f\r\n", tz);
} else {
TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
}
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);
}
@@ -0,0 +1,12 @@
#pragma once
#include <cli/cli.h>
#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();
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

@@ -0,0 +1,60 @@
// Base32 implementation
//
// Copyright 2010 Google Inc.
// Author: Markus Gutschke
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "base32.h"
int base32_decode(const uint8_t* encoded, uint8_t* result, int bufSize) {
int buffer = 0;
int bitsLeft = 0;
int count = 0;
for(const uint8_t* ptr = encoded; count < bufSize && *ptr; ++ptr) {
uint8_t ch = *ptr;
if(ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-') {
continue;
}
buffer <<= 5;
// Deal with commonly mistyped characters
if(ch == '0') {
ch = 'O';
} else if(ch == '1') {
ch = 'L';
} else if(ch == '8') {
ch = 'B';
}
// Look up one base32 digit
if((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
ch = (ch & 0x1F) - 1;
} else if(ch >= '2' && ch <= '7') {
ch -= '2' - 26;
} else {
return -1;
}
buffer |= ch;
bitsLeft += 5;
if(bitsLeft >= 8) {
result[count++] = buffer >> (bitsLeft - 8);
bitsLeft -= 8;
}
}
if(count < bufSize) {
result[count] = '\000';
}
return count;
}
@@ -0,0 +1,39 @@
// Base32 implementation
//
// Copyright 2010 Google Inc.
// Author: Markus Gutschke
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Encode and decode from base32 encoding using the following alphabet:
// ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
// This alphabet is documented in RFC 4648/3548
//
// We allow white-space and hyphens, but all other characters are considered
// invalid.
//
// All functions return the number of output bytes or -1 on error. If the
// output buffer is too small, the result will silently be truncated.
#pragma once
#include <stdint.h>
/**
* @brief Decodes Base-32 encoded bytes into plain bytes.
* @param encoded Base-32 encoded bytes
* @param[out] result result output buffer
* @param bufSize result output buffer size
* @return Decoded result length in bytes if successfully decoded; \c -1 otherwise
*/
int base32_decode(const uint8_t* encoded, uint8_t* result, int bufSize);
@@ -0,0 +1,136 @@
#include "list.h"
ListNode* list_init_head(void* data) {
ListNode* new = malloc(sizeof(ListNode));
if(new == NULL) return NULL;
new->data = data;
new->next = NULL;
return new;
}
ListNode* list_add(ListNode* head, void* data) {
ListNode* new = malloc(sizeof(ListNode));
if(new == NULL) return NULL;
new->data = data;
new->next = NULL;
if(head == NULL)
head = new;
else {
ListNode* it;
for(it = head; it->next != NULL; it = it->next)
;
it->next = new;
}
return head;
}
ListNode* list_find(ListNode* head, const void* data) {
ListNode* it = NULL;
for(it = head; it != NULL; it = it->next)
if(it->data == data) break;
return it;
}
ListNode* list_element_at(ListNode* head, uint16_t index) {
ListNode* it;
uint16_t i;
for(it = head, i = 0; it != NULL && i < index; it = it->next, i++)
;
return it;
}
ListNode* list_remove(ListNode* head, ListNode* ep) {
if(head == NULL) {
return NULL;
}
if(head == ep) {
ListNode* new_head = head->next;
free(head);
return new_head;
}
ListNode* it;
for(it = head; it->next != ep; it = it->next)
;
it->next = ep->next;
free(ep);
return head;
}
ListNode* list_remove_at(ListNode* head, uint16_t index, void** removed_node_data) {
if(head == NULL) {
return NULL;
}
ListNode* it;
ListNode* prev = NULL;
uint16_t i;
for(it = head, i = 0; it != NULL && i < index; prev = it, it = it->next, i++)
;
if(it == NULL) return head;
ListNode* new_head = head;
if(prev == NULL) {
new_head = it->next;
} else {
prev->next = it->next;
}
if(removed_node_data != NULL) {
*removed_node_data = it->data;
}
free(it);
return new_head;
}
ListNode* list_insert_at(ListNode* head, uint16_t index, void* data) {
if(index == 0 || head == NULL) {
ListNode* new_head = list_init_head(data);
if(new_head != NULL) {
new_head->next = head;
}
return new_head;
}
ListNode* it;
ListNode* prev = NULL;
uint16_t i;
for(it = head, i = 0; it != NULL && i < index; prev = it, it = it->next, i++)
;
ListNode* new = malloc(sizeof(ListNode));
if(new == NULL) return NULL;
new->data = data;
new->next = it;
prev->next = new;
return head;
}
void list_free(ListNode* head) {
ListNode* it = head;
ListNode* tmp;
while(it != NULL) {
tmp = it;
it = it->next;
free(tmp);
}
}
@@ -0,0 +1,102 @@
#pragma once
#include <stdlib.h>
#include <inttypes.h>
/**
* @brief Single linked list node
*/
typedef struct ListNode {
/**
* @brief Pointer to the data assigned to the current list node
*/
void* data;
/**
* @brief Pointer to the next list node
*/
struct ListNode* next;
} ListNode;
/**
* @brief Initializes a new list node head
* @param data data to be assigned to the head list node
* @return Head list node
*/
ListNode* list_init_head(void* data);
/**
* @brief Adds new list node to the end of the list
* @param head head list node
* @param data data to be assigned to the newly added list node
* @return Head list node
*/
ListNode* list_add(
ListNode* head,
void* data); /* adds element with specified data to the end of the list and returns new head node. */
/**
* @brief Searches list node with the given assigned \p data in the list
* @param head head list node
* @param data data to be searched
* @return List node containing \p data if there is such a node in the list; \c NULL otherwise
*/
ListNode* list_find(ListNode* head, const void* data);
/**
* @brief Searches list node with the given \p index in the list
* @param head head list node
* @param index desired list node index
* @return List node with the given \p index in the list if there is such a list node; \c NULL otherwise
*/
ListNode* list_element_at(ListNode* head, uint16_t index);
/**
* @brief Removes list node from the list
* @param head head list node
* @param ep list node to be removed
* @return Head list node
*/
ListNode* list_remove(ListNode* head, ListNode* ep);
/**
* @brief Removes list node with the given \p index in the list from the list
* @param head head list node
* @param index index of the node to be removed
* @param[out] removed_node_data data which was assigned to the removed list node
* @return Head list node
*/
ListNode* list_remove_at(ListNode* head, uint16_t index, void** removed_node_data);
/**
* @brief Inserts new list node at the given index
* @param head head list node
* @param index index in the list where the new list node should be inserted
* @param data data to be assgned to the new list node
* @return Head list node
*/
ListNode* list_insert_at(ListNode* head, uint16_t index, void* data);
/**
* @brief Disposes all the list nodes in the list
* @param head head list node
*/
void list_free(ListNode* head);
#define TOTP_LIST_INIT_OR_ADD(head, item, assert) \
do { \
if(head == NULL) { \
head = list_init_head(item); \
assert(head != NULL); \
} else { \
assert(list_add(head, item) != NULL); \
} \
} while(false)
#define TOTP_LIST_FOREACH(head, node, action) \
do { \
ListNode* node = head; \
while(node != NULL) { \
action node = node->next; \
} \
} while(false)
@@ -0,0 +1,22 @@
#include "memset_s.h"
#define RSIZE_MAX 0x7fffffffffffffffUL
errno_t memset_s(void* s, rsize_t smax, int c, rsize_t n) {
if(!s || smax > RSIZE_MAX) {
return EINVAL;
}
errno_t violation_present = 0;
if(n > smax) {
n = smax;
violation_present = EINVAL;
}
volatile unsigned char* v = s;
for(rsize_t i = 0u; i < n; ++i) {
*v++ = (unsigned char)c;
}
return violation_present;
}
@@ -0,0 +1,23 @@
#pragma once
#include <errno.h>
#include <stdint.h>
#ifndef _RSIZE_T_DECLARED
typedef uint64_t rsize_t;
#define _RSIZE_T_DECLARED
#endif
#ifndef _ERRNOT_DECLARED
typedef int16_t errno_t; //-V677
#define _ERRNOT_DECLARED
#endif
/**
* @brief Copies the value \p c into each of the first \p n characters of the object pointed to by \p s.
* @param s pointer to the object to fill
* @param smax size of the destination object
* @param c fill byte
* @param n number of bytes to fill
* @return \c 0 on success; non-zero otherwise
*/
errno_t memset_s(void* s, rsize_t smax, int c, rsize_t n);
@@ -0,0 +1,11 @@
#include "strnlen.h"
size_t strnlen(const char* s, size_t maxlen) {
size_t len;
for(len = 0; len < maxlen; len++, s++) {
if(!*s) break;
}
return len;
}
@@ -0,0 +1,3 @@
#include <stddef.h>
size_t strnlen(const char* s, size_t maxlen);
@@ -0,0 +1,28 @@
#include "roll_value.h"
#define TOTP_ROLL_VALUE_FN(type, step_type) \
TOTP_ROLL_VALUE_FN_HEADER(type, step_type) { \
type v = *value; \
if(step > 0 && v > max - step) { \
if(overflow_behavior == RollOverflowBehaviorRoll) { \
v = min; \
} else if(overflow_behavior == RollOverflowBehaviorStop) { \
v = max; \
} \
} else if(step < 0 && v < min - step) { \
if(overflow_behavior == RollOverflowBehaviorRoll) { \
v = max; \
} else if(overflow_behavior == RollOverflowBehaviorStop) { \
v = min; \
} \
} else { \
v += step; \
} \
*value = v; \
}
TOTP_ROLL_VALUE_FN(int8_t, int8_t)
TOTP_ROLL_VALUE_FN(uint8_t, int8_t)
TOTP_ROLL_VALUE_FN(uint16_t, int16_t);
@@ -0,0 +1,58 @@
#pragma once
#include <stdint.h>
typedef uint8_t TotpRollValueOverflowBehavior;
enum TotpRollValueOverflowBehaviors {
/**
* @brief Do not change value if it reached constraint
*/
RollOverflowBehaviorStop,
/**
* @brief Set value to opposite constraint value if it reached constraint
*/
RollOverflowBehaviorRoll
};
#define TOTP_ROLL_VALUE_FN_HEADER(type, step_type) \
void totp_roll_value_##type( \
type* value, \
step_type step, \
type min, \
type max, \
TotpRollValueOverflowBehavior overflow_behavior)
/**
* @brief Rolls \c int8_t \p value using \p min and \p max as an value constraints with \p step step.
* When value reaches constraint value \p overflow_behavior defines what to do next.
* @param[in,out] value value to roll
* @param step step to be used to change value
* @param min minimal possible value
* @param max maximum possible value
* @param overflow_behavior defines what to do when value reaches constraint value
*/
TOTP_ROLL_VALUE_FN_HEADER(int8_t, int8_t);
/**
* @brief Rolls \c uint8_t \p value using \p min and \p max as an value constraints with \p step step.
* When value reaches constraint value \p overflow_behavior defines what to do next.
* @param[in,out] value value to roll
* @param step step to be used to change value
* @param min minimal possible value
* @param max maximum possible value
* @param overflow_behavior defines what to do when value reaches constraint value
*/
TOTP_ROLL_VALUE_FN_HEADER(uint8_t, int8_t);
/**
* @brief Rolls \c uint16_t \p value using \p min and \p max as an value constraints with \p step step.
* When value reaches constraint value \p overflow_behavior defines what to do next.
* @param[in,out] value value to roll
* @param step step to be used to change value
* @param min minimal possible value
* @param max maximum possible value
* @param overflow_behavior defines what to do when value reaches constraint value
*/
TOTP_ROLL_VALUE_FN_HEADER(uint16_t, int16_t);
@@ -0,0 +1,16 @@
#include "timezone_utils.h"
int32_t timezone_offset_from_hours(float hours) {
return hours * 3600.0f;
}
uint64_t timezone_offset_apply(uint64_t time, int32_t offset) {
uint64_t for_time_adjusted;
if(offset > 0) {
for_time_adjusted = time - offset;
} else {
for_time_adjusted = time + (-offset);
}
return for_time_adjusted;
}
@@ -0,0 +1,18 @@
#pragma once
#include <inttypes.h>
/**
* @brief Calculates timezone offset in seconds given timezone offset in hours.
* @param hours timezone offset in hours
* @return Timezone offset in seconds.
*/
int32_t timezone_offset_from_hours(float hours);
/**
* @brief Applies timezone offset to a given time.
* @param time time to apply offset to.
* @param offset timezone offset in seconds.
* @return Time with timezone offset applied.
*/
uint64_t timezone_offset_apply(uint64_t time, int32_t offset);
@@ -0,0 +1,733 @@
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include "../list/list.h"
#include "../../types/common.h"
#include "../../types/token_info.h"
#include "migrations/config_migration_v1_to_v2.h"
#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("authenticator")
#define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/totp.conf"
#define CONFIG_FILE_BACKUP_PATH CONFIG_FILE_PATH ".backup"
#define CONFIG_FILE_TEMP_PATH CONFIG_FILE_PATH ".tmp"
#define CONFIG_FILE_ORIG_PATH CONFIG_FILE_PATH ".orig"
#define CONFIG_FILE_PATH_PREVIOUS EXT_PATH("apps/Misc") "/totp.conf"
static char* token_info_get_algo_as_cstr(const TokenInfo* token_info) {
switch(token_info->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;
default:
break;
}
return NULL;
}
static void token_info_set_algo_from_str(TokenInfo* token_info, const 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) == 0) {
token_info->algo = SHA256;
} else if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME) == 0) {
token_info->algo = SHA512;
}
}
/**
* @brief Opens storage record
* @return Storage record
*/
static Storage* totp_open_storage() {
return furi_record_open(RECORD_STORAGE);
}
/**
* @brief Closes storage record
*/
static void totp_close_storage() {
furi_record_close(RECORD_STORAGE);
}
/**
* @brief Closes config file
* @param file config file reference
*/
static void totp_close_config_file(FlipperFormat* file) {
if(file == NULL) return;
flipper_format_file_close(file);
flipper_format_free(file);
}
/**
* @brief Opens or creates TOTP application standard config file
* @param storage storage record to use
* @param[out] file opened config file
* @return Config file open result
*/
static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperFormat** file) {
FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
if(storage_common_stat(storage, CONFIG_FILE_PATH, NULL) == FSE_OK) {
FURI_LOG_D(LOGGING_TAG, "Config file %s found", CONFIG_FILE_PATH);
if(!flipper_format_file_open_existing(fff_data_file, CONFIG_FILE_PATH)) {
FURI_LOG_E(LOGGING_TAG, "Error opening existing file %s", CONFIG_FILE_PATH);
totp_close_config_file(fff_data_file);
return TotpConfigFileOpenError;
}
} else if(storage_common_stat(storage, CONFIG_FILE_PATH_PREVIOUS, NULL) == FSE_OK) {
FURI_LOG_D(LOGGING_TAG, "Old config file %s found", CONFIG_FILE_PATH_PREVIOUS);
if(storage_common_stat(storage, CONFIG_FILE_DIRECTORY_PATH, NULL) == FSE_NOT_EXIST) {
FURI_LOG_D(
LOGGING_TAG,
"Directory %s doesn't exist. Will create new.",
CONFIG_FILE_DIRECTORY_PATH);
if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) {
FURI_LOG_E(LOGGING_TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH);
totp_close_config_file(fff_data_file);
return TotpConfigFileOpenError;
}
}
if(storage_common_rename(storage, CONFIG_FILE_PATH_PREVIOUS, CONFIG_FILE_PATH) != FSE_OK) {
FURI_LOG_E(LOGGING_TAG, "Error moving config to %s", CONFIG_FILE_PATH);
totp_close_config_file(fff_data_file);
return TotpConfigFileOpenError;
}
FURI_LOG_I(LOGGING_TAG, "Applied config file path migration");
return totp_open_config_file(storage, file);
} else {
FURI_LOG_D(LOGGING_TAG, "Config file %s is not found. Will create new.", CONFIG_FILE_PATH);
if(storage_common_stat(storage, CONFIG_FILE_DIRECTORY_PATH, NULL) == FSE_NOT_EXIST) {
FURI_LOG_D(
LOGGING_TAG,
"Directory %s doesn't exist. Will create new.",
CONFIG_FILE_DIRECTORY_PATH);
if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) {
FURI_LOG_E(LOGGING_TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH);
return TotpConfigFileOpenError;
}
}
if(!flipper_format_file_open_new(fff_data_file, CONFIG_FILE_PATH)) {
totp_close_config_file(fff_data_file);
FURI_LOG_E(LOGGING_TAG, "Error creating new file %s", CONFIG_FILE_PATH);
return TotpConfigFileOpenError;
}
flipper_format_write_header_cstr(
fff_data_file, CONFIG_FILE_HEADER, CONFIG_FILE_ACTUAL_VERSION);
float tmp_tz = 0;
flipper_format_write_comment_cstr(fff_data_file, " ");
flipper_format_write_comment_cstr(
fff_data_file,
"Timezone offset in hours. Important note: do not put '+' sign for positive values");
flipper_format_write_float(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, &tmp_tz, 1);
uint32_t tmp_uint32 = NotificationMethodSound | NotificationMethodVibro;
flipper_format_write_comment_cstr(fff_data_file, " ");
flipper_format_write_comment_cstr(
fff_data_file,
"How to notify user when new token is generated or badusb mode is activated (possible values: 0 - do not notify, 1 - sound, 2 - vibro, 3 sound and vibro)");
flipper_format_write_uint32(
fff_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1);
FuriString* temp_str = furi_string_alloc();
flipper_format_write_comment_cstr(fff_data_file, " ");
flipper_format_write_comment_cstr(fff_data_file, "=== TOKEN SAMPLE BEGIN ===");
flipper_format_write_comment_cstr(fff_data_file, " ");
flipper_format_write_comment_cstr(
fff_data_file, "# Token name which will be visible in the UI.");
furi_string_printf(temp_str, "%s: Sample token name", TOTP_CONFIG_KEY_TOKEN_NAME);
flipper_format_write_comment(fff_data_file, temp_str);
flipper_format_write_comment_cstr(fff_data_file, " ");
flipper_format_write_comment_cstr(
fff_data_file,
"# Plain token secret without spaces, dashes and etc, just pure alpha-numeric characters. Important note: plain token will be encrypted and replaced by TOTP app");
furi_string_printf(temp_str, "%s: plaintokensecret", TOTP_CONFIG_KEY_TOKEN_SECRET);
flipper_format_write_comment(fff_data_file, temp_str);
flipper_format_write_comment_cstr(fff_data_file, " ");
furi_string_printf(
temp_str,
" # Token hashing algorithm to use during code generation. Supported options are %s, %s and %s. If you are not use which one to use - use %s",
TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME,
TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME,
TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME,
TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME);
flipper_format_write_comment(fff_data_file, temp_str);
furi_string_printf(
temp_str, "%s: %s", TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME);
flipper_format_write_comment(fff_data_file, temp_str);
flipper_format_write_comment_cstr(fff_data_file, " ");
flipper_format_write_comment_cstr(
fff_data_file,
"# How many digits there should be in generated code. Available options are 6 and 8. Majority websites requires 6 digits code, however some rare websites wants to get 8 digits code. If you are not sure which one to use - use 6");
furi_string_printf(temp_str, "%s: 6", TOTP_CONFIG_KEY_TOKEN_DIGITS);
flipper_format_write_comment(fff_data_file, temp_str);
flipper_format_write_comment_cstr(fff_data_file, " ");
flipper_format_write_comment_cstr(fff_data_file, "=== TOKEN SAMPLE END ===");
flipper_format_write_comment_cstr(fff_data_file, " ");
furi_string_free(temp_str);
if(!flipper_format_rewind(fff_data_file)) {
totp_close_config_file(fff_data_file);
FURI_LOG_E(LOGGING_TAG, "Rewind error");
return TotpConfigFileOpenError;
}
}
*file = fff_data_file;
return TotpConfigFileOpenSuccess;
}
TotpConfigFileUpdateResult
totp_config_file_save_new_token_i(FlipperFormat* file, const TokenInfo* token_info) {
TotpConfigFileUpdateResult update_result;
do {
if(!flipper_format_seek_to_end(file)) {
update_result = TotpConfigFileUpdateError;
break;
}
if(!flipper_format_write_string_cstr(file, TOTP_CONFIG_KEY_TOKEN_NAME, token_info->name)) {
update_result = TotpConfigFileUpdateError;
break;
}
bool token_is_valid = token_info->token != NULL && token_info->token_length > 0;
if(!token_is_valid &&
!flipper_format_write_comment_cstr(file, "!!! WARNING BEGIN: INVALID TOKEN !!!")) {
update_result = TotpConfigFileUpdateError;
break;
}
if(!flipper_format_write_hex(
file, TOTP_CONFIG_KEY_TOKEN_SECRET, token_info->token, token_info->token_length)) {
update_result = TotpConfigFileUpdateError;
break;
}
if(!token_is_valid && !flipper_format_write_comment_cstr(file, "!!! WARNING END !!!")) {
update_result = TotpConfigFileUpdateError;
break;
}
if(!flipper_format_write_string_cstr(
file, TOTP_CONFIG_KEY_TOKEN_ALGO, token_info_get_algo_as_cstr(token_info))) {
update_result = TotpConfigFileUpdateError;
break;
}
uint32_t tmp_uint32 = token_info->digits;
if(!flipper_format_write_uint32(file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &tmp_uint32, 1)) {
update_result = TotpConfigFileUpdateError;
break;
}
update_result = TotpConfigFileUpdateSuccess;
} while(false);
return update_result;
}
TotpConfigFileUpdateResult totp_config_file_save_new_token(const TokenInfo* token_info) {
Storage* cfg_storage = totp_open_storage();
FlipperFormat* file;
TotpConfigFileUpdateResult update_result;
if(totp_open_config_file(cfg_storage, &file) == TotpConfigFileOpenSuccess) {
do {
if(totp_config_file_save_new_token_i(file, token_info) !=
TotpConfigFileUpdateSuccess) {
update_result = TotpConfigFileUpdateError;
break;
}
update_result = TotpConfigFileUpdateSuccess;
} while(false);
totp_close_config_file(file);
} else {
update_result = TotpConfigFileUpdateError;
}
totp_close_storage();
return update_result;
}
TotpConfigFileUpdateResult totp_config_file_update_timezone_offset(float new_timezone_offset) {
Storage* cfg_storage = totp_open_storage();
FlipperFormat* file;
TotpConfigFileUpdateResult update_result;
if(totp_open_config_file(cfg_storage, &file) == TotpConfigFileOpenSuccess) {
do {
if(!flipper_format_insert_or_update_float(
file, TOTP_CONFIG_KEY_TIMEZONE, &new_timezone_offset, 1)) {
update_result = TotpConfigFileUpdateError;
break;
}
update_result = TotpConfigFileUpdateSuccess;
} while(false);
totp_close_config_file(file);
} else {
update_result = TotpConfigFileUpdateError;
}
totp_close_storage();
return update_result;
}
TotpConfigFileUpdateResult
totp_config_file_update_notification_method(NotificationMethod new_notification_method) {
Storage* cfg_storage = totp_open_storage();
FlipperFormat* file;
TotpConfigFileUpdateResult update_result;
if(totp_open_config_file(cfg_storage, &file) == TotpConfigFileOpenSuccess) {
do {
uint32_t tmp_uint32 = new_notification_method;
if(!flipper_format_insert_or_update_uint32(
file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) {
update_result = TotpConfigFileUpdateError;
break;
}
update_result = TotpConfigFileUpdateSuccess;
} while(false);
totp_close_config_file(file);
} else {
update_result = TotpConfigFileUpdateError;
}
totp_close_storage();
return update_result;
}
TotpConfigFileUpdateResult totp_config_file_update_user_settings(const PluginState* plugin_state) {
Storage* cfg_storage = totp_open_storage();
FlipperFormat* file;
TotpConfigFileUpdateResult update_result;
if(totp_open_config_file(cfg_storage, &file) == TotpConfigFileOpenSuccess) {
do {
if(!flipper_format_insert_or_update_float(
file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1)) {
update_result = TotpConfigFileUpdateError;
break;
}
uint32_t tmp_uint32 = plugin_state->notification_method;
if(!flipper_format_insert_or_update_uint32(
file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) {
update_result = TotpConfigFileUpdateError;
break;
}
update_result = TotpConfigFileUpdateSuccess;
} while(false);
totp_close_config_file(file);
} else {
update_result = TotpConfigFileUpdateError;
}
totp_close_storage();
return update_result;
}
TotpConfigFileUpdateResult totp_full_save_config_file(const PluginState* const plugin_state) {
Storage* storage = totp_open_storage();
FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
TotpConfigFileUpdateResult result = TotpConfigFileUpdateSuccess;
do {
if(!flipper_format_file_open_always(fff_data_file, CONFIG_FILE_TEMP_PATH)) {
result = TotpConfigFileUpdateError;
break;
}
if(!flipper_format_write_header_cstr(
fff_data_file, CONFIG_FILE_HEADER, CONFIG_FILE_ACTUAL_VERSION)) {
result = TotpConfigFileUpdateError;
break;
}
if(!flipper_format_write_hex(
fff_data_file, TOTP_CONFIG_KEY_BASE_IV, &plugin_state->base_iv[0], TOTP_IV_SIZE)) {
result = TotpConfigFileUpdateError;
break;
}
if(!flipper_format_write_hex(
fff_data_file,
TOTP_CONFIG_KEY_CRYPTO_VERIFY,
plugin_state->crypto_verify_data,
plugin_state->crypto_verify_data_length)) {
result = TotpConfigFileUpdateError;
break;
}
if(!flipper_format_write_float(
fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1)) {
result = TotpConfigFileUpdateError;
break;
}
if(!flipper_format_write_bool(
fff_data_file, TOTP_CONFIG_KEY_PINSET, &plugin_state->pin_set, 1)) {
result = TotpConfigFileUpdateError;
break;
}
uint32_t tmp_uint32 = plugin_state->notification_method;
if(!flipper_format_write_uint32(
fff_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) {
result = TotpConfigFileUpdateError;
break;
}
bool tokens_written = true;
TOTP_LIST_FOREACH(plugin_state->tokens_list, node, {
const TokenInfo* token_info = node->data;
tokens_written = tokens_written &&
totp_config_file_save_new_token_i(fff_data_file, token_info) ==
TotpConfigFileUpdateSuccess;
});
if(!tokens_written) {
result = TotpConfigFileUpdateError;
break;
}
} while(false);
totp_close_config_file(fff_data_file);
if(result == TotpConfigFileUpdateSuccess) {
if(storage_file_exists(storage, CONFIG_FILE_ORIG_PATH)) {
storage_simply_remove(storage, CONFIG_FILE_ORIG_PATH);
}
if(storage_common_rename(storage, CONFIG_FILE_PATH, CONFIG_FILE_ORIG_PATH) != FSE_OK) {
result = TotpConfigFileUpdateError;
} else if(storage_common_rename(storage, CONFIG_FILE_TEMP_PATH, CONFIG_FILE_PATH) != FSE_OK) {
result = TotpConfigFileUpdateError;
} else if(!storage_simply_remove(storage, CONFIG_FILE_ORIG_PATH)) {
result = TotpConfigFileUpdateError;
}
}
totp_close_storage();
return result;
}
TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_state) {
Storage* storage = totp_open_storage();
FlipperFormat* fff_data_file;
TotpConfigFileOpenResult result;
if((result = totp_open_config_file(storage, &fff_data_file)) != TotpConfigFileOpenSuccess) {
totp_close_storage();
return result;
}
plugin_state->timezone_offset = 0;
FuriString* temp_str = furi_string_alloc();
do {
uint32_t file_version;
if(!flipper_format_read_header(fff_data_file, temp_str, &file_version)) {
FURI_LOG_E(LOGGING_TAG, "Missing or incorrect header");
result = TotpConfigFileOpenError;
break;
}
if(file_version < CONFIG_FILE_ACTUAL_VERSION) {
FURI_LOG_I(
LOGGING_TAG,
"Obsolete config file version detected. Current version: %" PRIu32
"; Actual version: %" PRId16,
file_version,
CONFIG_FILE_ACTUAL_VERSION);
totp_close_config_file(fff_data_file);
if(storage_common_stat(storage, CONFIG_FILE_BACKUP_PATH, NULL) == FSE_OK) {
storage_simply_remove(storage, CONFIG_FILE_BACKUP_PATH);
}
if(storage_common_copy(storage, CONFIG_FILE_PATH, CONFIG_FILE_BACKUP_PATH) == FSE_OK) {
FURI_LOG_I(LOGGING_TAG, "Took config file backup to %s", CONFIG_FILE_BACKUP_PATH);
if(totp_open_config_file(storage, &fff_data_file) != TotpConfigFileOpenSuccess) {
result = TotpConfigFileOpenError;
break;
}
FlipperFormat* fff_backup_data_file = flipper_format_file_alloc(storage);
if(!flipper_format_file_open_existing(
fff_backup_data_file, CONFIG_FILE_BACKUP_PATH)) {
flipper_format_file_close(fff_backup_data_file);
flipper_format_free(fff_backup_data_file);
result = TotpConfigFileOpenError;
break;
}
if(file_version == 1) {
if(totp_config_migrate_v1_to_v2(fff_data_file, fff_backup_data_file)) {
FURI_LOG_I(LOGGING_TAG, "Applied migration from v1 to v2");
} else {
FURI_LOG_W(
LOGGING_TAG, "An error occurred during migration from v1 to v2");
result = TotpConfigFileOpenError;
break;
}
}
flipper_format_file_close(fff_backup_data_file);
flipper_format_free(fff_backup_data_file);
flipper_format_rewind(fff_data_file);
} else {
FURI_LOG_E(
LOGGING_TAG,
"An error occurred during taking backup of %s into %s before migration",
CONFIG_FILE_PATH,
CONFIG_FILE_BACKUP_PATH);
result = TotpConfigFileOpenError;
break;
}
}
if(!flipper_format_read_hex(
fff_data_file, TOTP_CONFIG_KEY_BASE_IV, &plugin_state->base_iv[0], TOTP_IV_SIZE)) {
FURI_LOG_D(LOGGING_TAG, "Missing base IV");
}
if(!flipper_format_rewind(fff_data_file)) {
result = TotpConfigFileOpenError;
break;
}
uint32_t crypto_size;
if(flipper_format_get_value_count(
fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, &crypto_size) &&
crypto_size > 0) {
plugin_state->crypto_verify_data = malloc(sizeof(uint8_t) * crypto_size);
furi_check(plugin_state->crypto_verify_data != NULL);
plugin_state->crypto_verify_data_length = crypto_size;
if(!flipper_format_read_hex(
fff_data_file,
TOTP_CONFIG_KEY_CRYPTO_VERIFY,
plugin_state->crypto_verify_data,
crypto_size)) {
FURI_LOG_D(LOGGING_TAG, "Missing crypto verify token");
free(plugin_state->crypto_verify_data);
plugin_state->crypto_verify_data = NULL;
plugin_state->crypto_verify_data_length = 0;
}
} else {
plugin_state->crypto_verify_data = NULL;
plugin_state->crypto_verify_data_length = 0;
}
if(!flipper_format_rewind(fff_data_file)) {
result = TotpConfigFileOpenError;
break;
}
if(!flipper_format_read_float(
fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1)) {
plugin_state->timezone_offset = 0;
FURI_LOG_D(LOGGING_TAG, "Missing timezone offset information, defaulting to 0");
}
if(!flipper_format_rewind(fff_data_file)) {
result = TotpConfigFileOpenError;
break;
}
if(!flipper_format_read_bool(
fff_data_file, TOTP_CONFIG_KEY_PINSET, &plugin_state->pin_set, 1)) {
plugin_state->pin_set = true;
}
flipper_format_rewind(fff_data_file);
uint32_t tmp_uint32;
if(!flipper_format_read_uint32(
fff_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) {
tmp_uint32 = NotificationMethodSound | NotificationMethodVibro;
}
plugin_state->notification_method = tmp_uint32;
} while(false);
furi_string_free(temp_str);
totp_close_config_file(fff_data_file);
totp_close_storage();
return result;
}
TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state) {
Storage* storage = totp_open_storage();
FlipperFormat* fff_data_file;
if(totp_open_config_file(storage, &fff_data_file) != TotpConfigFileOpenSuccess) {
totp_close_storage();
return TokenLoadingResultError;
}
FuriString* temp_str = furi_string_alloc();
uint32_t temp_data32;
if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) {
FURI_LOG_E(LOGGING_TAG, "Missing or incorrect header");
totp_close_storage();
furi_string_free(temp_str);
return TokenLoadingResultError;
}
TokenLoadingResult result = TokenLoadingResultSuccess;
uint16_t index = 0;
bool has_any_plain_secret = false;
while(true) {
if(!flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str)) {
break;
}
TokenInfo* tokenInfo = token_info_alloc();
size_t temp_cstr_len = furi_string_size(temp_str);
tokenInfo->name = malloc(temp_cstr_len + 1);
furi_check(tokenInfo->name != NULL);
strlcpy(tokenInfo->name, furi_string_get_cstr(temp_str), temp_cstr_len + 1);
uint32_t secret_bytes_count;
if(!flipper_format_get_value_count(
fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, &secret_bytes_count)) {
secret_bytes_count = 0;
}
if(secret_bytes_count == 1) { // Plain secret key
if(flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str)) {
if(token_info_set_secret(
tokenInfo,
furi_string_get_cstr(temp_str),
furi_string_size(temp_str),
&plugin_state->iv[0])) {
FURI_LOG_W(LOGGING_TAG, "Token \"%s\" has plain secret", tokenInfo->name);
} else {
tokenInfo->token = NULL;
tokenInfo->token_length = 0;
FURI_LOG_W(LOGGING_TAG, "Token \"%s\" has invalid secret", tokenInfo->name);
result = TokenLoadingResultWarning;
}
} else {
tokenInfo->token = NULL;
tokenInfo->token_length = 0;
result = TokenLoadingResultWarning;
}
has_any_plain_secret = true;
} else { // encrypted
tokenInfo->token_length = secret_bytes_count;
if(secret_bytes_count > 0) {
tokenInfo->token = malloc(tokenInfo->token_length);
furi_check(tokenInfo->token != NULL);
if(!flipper_format_read_hex(
fff_data_file,
TOTP_CONFIG_KEY_TOKEN_SECRET,
tokenInfo->token,
tokenInfo->token_length)) {
free(tokenInfo->token);
tokenInfo->token = NULL;
tokenInfo->token_length = 0;
result = TokenLoadingResultWarning;
}
} else {
tokenInfo->token = NULL;
result = TokenLoadingResultWarning;
}
}
if(flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str)) {
token_info_set_algo_from_str(tokenInfo, temp_str);
} else {
tokenInfo->algo = SHA1;
}
if(!flipper_format_read_uint32(
fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &temp_data32, 1) ||
!token_info_set_digits_from_int(tokenInfo, temp_data32)) {
tokenInfo->digits = TOTP_6_DIGITS;
}
FURI_LOG_D(LOGGING_TAG, "Found token \"%s\"", tokenInfo->name);
TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, tokenInfo, furi_check);
index++;
}
plugin_state->tokens_count = index;
plugin_state->token_list_loaded = true;
FURI_LOG_D(LOGGING_TAG, "Found %" PRIu16 " tokens", index);
furi_string_free(temp_str);
totp_close_config_file(fff_data_file);
totp_close_storage();
if(has_any_plain_secret) {
totp_full_save_config_file(plugin_state);
}
return result;
}
TotpConfigFileUpdateResult
totp_config_file_update_crypto_signatures(const PluginState* plugin_state) {
Storage* storage = totp_open_storage();
FlipperFormat* config_file;
TotpConfigFileUpdateResult update_result;
if(totp_open_config_file(storage, &config_file) == TotpConfigFileOpenSuccess) {
do {
if(!flipper_format_insert_or_update_hex(
config_file, TOTP_CONFIG_KEY_BASE_IV, plugin_state->base_iv, TOTP_IV_SIZE)) {
update_result = TotpConfigFileUpdateError;
break;
}
if(!flipper_format_insert_or_update_hex(
config_file,
TOTP_CONFIG_KEY_CRYPTO_VERIFY,
plugin_state->crypto_verify_data,
plugin_state->crypto_verify_data_length)) {
update_result = TotpConfigFileUpdateError;
break;
}
if(!flipper_format_insert_or_update_bool(
config_file, TOTP_CONFIG_KEY_PINSET, &plugin_state->pin_set, 1)) {
update_result = TotpConfigFileUpdateError;
break;
}
update_result = TotpConfigFileUpdateSuccess;
} while(false);
totp_close_config_file(config_file);
} else {
update_result = TotpConfigFileUpdateError;
}
totp_close_storage();
return update_result;
}
@@ -0,0 +1,119 @@
#pragma once
#include <flipper_format/flipper_format.h>
#include <furi.h>
#include "../../types/plugin_state.h"
#include "../../types/token_info.h"
#include "constants.h"
typedef uint8_t TokenLoadingResult;
typedef uint8_t TotpConfigFileOpenResult;
typedef uint8_t TotpConfigFileUpdateResult;
/**
* @brief Token loading results
*/
enum TokenLoadingResults {
/**
* @brief All the tokens loaded successfully
*/
TokenLoadingResultSuccess,
/**
* @brief All the tokens loaded, but there are some warnings
*/
TokenLoadingResultWarning,
/**
* @brief Tokens not loaded because of error(s)
*/
TokenLoadingResultError
};
/**
* @brief Config file opening result
*/
enum TotpConfigFileOpenResults {
/**
* @brief Config file opened successfully
*/
TotpConfigFileOpenSuccess = 0,
/**
* @brief An error has occurred during opening config file
*/
TotpConfigFileOpenError = 1
};
/**
* @brief Config file updating result
*/
enum TotpConfigFileUpdateResults {
/**
* @brief Config file updated successfully
*/
TotpConfigFileUpdateSuccess,
/**
* @brief An error has occurred during updating config file
*/
TotpConfigFileUpdateError
};
/**
* @brief Saves all the settings and tokens to an application config file
* @param plugin_state application state
* @return Config file update result
*/
TotpConfigFileUpdateResult totp_full_save_config_file(const PluginState* const plugin_state);
/**
* @brief Loads basic information from an application config file into application state without loading all the tokens
* @param plugin_state application state
* @return Config file open result
*/
TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_state);
/**
* @brief Loads tokens from an application config file into application state
* @param plugin_state application state
* @return Results of the loading
*/
TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state);
/**
* @brief Add new token to the end of the application config file
* @param token_info token information to be saved
* @return Config file update result
*/
TotpConfigFileUpdateResult totp_config_file_save_new_token(const TokenInfo* token_info);
/**
* @brief Updates timezone offset in an application config file
* @param new_timezone_offset new timezone offset to be set
* @return Config file update result
*/
TotpConfigFileUpdateResult totp_config_file_update_timezone_offset(float new_timezone_offset);
/**
* @brief Updates notification method in an application config file
* @param new_notification_method new notification method to be set
* @return Config file update result
*/
TotpConfigFileUpdateResult
totp_config_file_update_notification_method(NotificationMethod new_notification_method);
/**
* @brief Updates application user settings
* @param plugin_state application state
* @return Config file update result
*/
TotpConfigFileUpdateResult totp_config_file_update_user_settings(const PluginState* plugin_state);
/**
* @brief Updates crypto signatures information
* @param plugin_state application state
* @return Config file update result
*/
TotpConfigFileUpdateResult
totp_config_file_update_crypto_signatures(const PluginState* plugin_state);
@@ -0,0 +1,18 @@
#pragma once
#define CONFIG_FILE_HEADER "Flipper TOTP plugin config file"
#define CONFIG_FILE_ACTUAL_VERSION 2
#define TOTP_CONFIG_KEY_TIMEZONE "Timezone"
#define TOTP_CONFIG_KEY_TOKEN_NAME "TokenName"
#define TOTP_CONFIG_KEY_TOKEN_SECRET "TokenSecret"
#define TOTP_CONFIG_KEY_TOKEN_ALGO "TokenAlgo"
#define TOTP_CONFIG_KEY_TOKEN_DIGITS "TokenDigits"
#define TOTP_CONFIG_KEY_CRYPTO_VERIFY "Crypto"
#define TOTP_CONFIG_KEY_BASE_IV "BaseIV"
#define TOTP_CONFIG_KEY_PINSET "PinIsSet"
#define TOTP_CONFIG_KEY_NOTIFICATION_METHOD "NotificationMethod"
#define TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME "sha1"
#define TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME "sha256"
#define TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME "sha512"
@@ -0,0 +1,46 @@
#include "config_migration_v1_to_v2.h"
#include <flipper_format/flipper_format.h>
#include "../constants.h"
#define NEW_VERSION 2
bool totp_config_migrate_v1_to_v2(
FlipperFormat* fff_data_file,
FlipperFormat* fff_backup_data_file) {
flipper_format_write_header_cstr(fff_data_file, CONFIG_FILE_HEADER, NEW_VERSION);
FuriString* temp_str = furi_string_alloc();
if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str)) {
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str);
}
if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str)) {
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str);
}
if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str)) {
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str);
}
while(true) {
if(!flipper_format_read_string(
fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str)) {
break;
}
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str);
flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str);
flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str);
flipper_format_write_string_cstr(
fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME);
uint32_t default_digits = 6;
flipper_format_write_uint32(
fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &default_digits, 1);
}
furi_string_free(temp_str);
return true;
}
@@ -0,0 +1,7 @@
#pragma once
#include <flipper_format/flipper_format.h>
bool totp_config_migrate_v1_to_v2(
FlipperFormat* fff_data_file,
FlipperFormat* fff_backup_data_file);
@@ -0,0 +1,4 @@
#pragma once
#define CONVERT_DIGIT_TO_CHAR(digit) ((digit) + '0')
#define CONVERT_CHAR_TO_DIGIT(ch) ((ch) - '0')
@@ -0,0 +1,133 @@
#include "crypto.h"
#include <furi.h>
#include <furi_hal.h>
#include "../config/config.h"
#include "../../types/common.h"
#include "memset_s.h"
#define CRYPTO_KEY_SLOT 2
#define CRYPTO_VERIFY_KEY "FFF_Crypto_pass"
#define CRYPTO_VERIFY_KEY_LENGTH 16
#define CRYPTO_ALIGNMENT_FACTOR 16
uint8_t* totp_crypto_encrypt(
const uint8_t* plain_data,
const size_t plain_data_length,
const uint8_t* iv,
size_t* encrypted_data_length) {
uint8_t* encrypted_data;
size_t remain = plain_data_length % CRYPTO_ALIGNMENT_FACTOR;
if(remain) {
size_t plain_data_aligned_length = plain_data_length - remain + CRYPTO_ALIGNMENT_FACTOR;
uint8_t* plain_data_aligned = malloc(plain_data_aligned_length);
furi_check(plain_data_aligned != NULL);
memset(plain_data_aligned, 0, plain_data_aligned_length);
memcpy(plain_data_aligned, plain_data, plain_data_length);
encrypted_data = malloc(plain_data_aligned_length);
furi_check(encrypted_data != NULL);
*encrypted_data_length = plain_data_aligned_length;
furi_hal_crypto_store_load_key(CRYPTO_KEY_SLOT, iv);
furi_hal_crypto_encrypt(plain_data_aligned, encrypted_data, plain_data_aligned_length);
furi_hal_crypto_store_unload_key(CRYPTO_KEY_SLOT);
memset_s(plain_data_aligned, plain_data_aligned_length, 0, plain_data_aligned_length);
free(plain_data_aligned);
} else {
encrypted_data = malloc(plain_data_length);
furi_check(encrypted_data != NULL);
*encrypted_data_length = plain_data_length;
furi_hal_crypto_store_load_key(CRYPTO_KEY_SLOT, iv);
furi_hal_crypto_encrypt(plain_data, encrypted_data, plain_data_length);
furi_hal_crypto_store_unload_key(CRYPTO_KEY_SLOT);
}
return encrypted_data;
}
uint8_t* totp_crypto_decrypt(
const uint8_t* encrypted_data,
const size_t encrypted_data_length,
const uint8_t* iv,
size_t* decrypted_data_length) {
*decrypted_data_length = encrypted_data_length;
uint8_t* decrypted_data = malloc(*decrypted_data_length);
furi_check(decrypted_data != NULL);
furi_hal_crypto_store_load_key(CRYPTO_KEY_SLOT, iv);
furi_hal_crypto_decrypt(encrypted_data, decrypted_data, encrypted_data_length);
furi_hal_crypto_store_unload_key(CRYPTO_KEY_SLOT);
return decrypted_data;
}
bool totp_crypto_seed_iv(PluginState* plugin_state, const uint8_t* pin, uint8_t pin_length) {
if(plugin_state->crypto_verify_data == NULL) {
FURI_LOG_D(LOGGING_TAG, "Generating new IV");
furi_hal_random_fill_buf(&plugin_state->base_iv[0], TOTP_IV_SIZE);
}
memcpy(&plugin_state->iv[0], &plugin_state->base_iv[0], TOTP_IV_SIZE);
if(pin != NULL && pin_length > 0) {
uint8_t max_i;
if(pin_length > TOTP_IV_SIZE) {
max_i = TOTP_IV_SIZE;
} else {
max_i = pin_length;
}
for(uint8_t i = 0; i < max_i; i++) {
plugin_state->iv[i] = plugin_state->iv[i] ^ (uint8_t)(pin[i] * (i + 1));
}
} else {
uint8_t max_i;
size_t uid_size = furi_hal_version_uid_size();
if(uid_size > TOTP_IV_SIZE) {
max_i = TOTP_IV_SIZE;
} else {
max_i = uid_size;
}
const uint8_t* uid = furi_hal_version_uid();
for(uint8_t i = 0; i < max_i; i++) {
plugin_state->iv[i] = plugin_state->iv[i] ^ uid[i];
}
}
bool result = true;
if(plugin_state->crypto_verify_data == NULL) {
FURI_LOG_D(LOGGING_TAG, "Generating crypto verify data");
plugin_state->crypto_verify_data = malloc(CRYPTO_VERIFY_KEY_LENGTH);
furi_check(plugin_state->crypto_verify_data != NULL);
plugin_state->crypto_verify_data_length = CRYPTO_VERIFY_KEY_LENGTH;
plugin_state->crypto_verify_data = totp_crypto_encrypt(
(uint8_t*)CRYPTO_VERIFY_KEY,
CRYPTO_VERIFY_KEY_LENGTH,
&plugin_state->iv[0],
&plugin_state->crypto_verify_data_length);
plugin_state->pin_set = pin != NULL && pin_length > 0;
result = totp_config_file_update_crypto_signatures(plugin_state) ==
TotpConfigFileUpdateSuccess;
}
return result;
}
bool totp_crypto_verify_key(const PluginState* plugin_state) {
size_t decrypted_key_length;
const uint8_t* decrypted_key = totp_crypto_decrypt(
plugin_state->crypto_verify_data,
plugin_state->crypto_verify_data_length,
&plugin_state->iv[0],
&decrypted_key_length);
bool key_valid = true;
for(uint8_t i = 0; i < CRYPTO_VERIFY_KEY_LENGTH && key_valid; i++) {
if(decrypted_key[i] != CRYPTO_VERIFY_KEY[i]) key_valid = false;
}
return key_valid;
}
@@ -0,0 +1,47 @@
#pragma once
#include "../../types/plugin_state.h"
/**
* @brief Encrypts plain data using built-in certificate and given initialization vector (IV)
* @param plain_data plain data to be encrypted
* @param plain_data_length plain data length
* @param iv initialization vector (IV) to be used to encrypt plain data
* @param[out] encrypted_data_length encrypted data length
* @return Encrypted data
*/
uint8_t* totp_crypto_encrypt(
const uint8_t* plain_data,
const size_t plain_data_length,
const uint8_t* iv,
size_t* encrypted_data_length);
/**
* @brief Decrypts encrypted data using built-in certificate and given initialization vector (IV)
* @param encrypted_data encrypted data to be decrypted
* @param encrypted_data_length encrypted data length
* @param iv initialization vector (IV) to be used to encrypt plain data
* @param[out] decrypted_data_length decrypted data length
* @return Decrypted data
*/
uint8_t* totp_crypto_decrypt(
const uint8_t* encrypted_data,
const size_t encrypted_data_length,
const uint8_t* iv,
size_t* decrypted_data_length);
/**
* @brief Seed initialization vector (IV) using user's PIN
* @param plugin_state application state
* @param pin user's PIN
* @param pin_length user's PIN length
* @return \c true on success; \c false otherwise
*/
bool totp_crypto_seed_iv(PluginState* plugin_state, const uint8_t* pin, uint8_t pin_length);
/**
* @brief Verifies whether cryptographic information (certificate + IV) is valid and can be used for encryption and decryption
* @param plugin_state application state
* @return \c true if cryptographic information is valid; \c false otherwise
*/
bool totp_crypto_verify_key(const PluginState* plugin_state);
@@ -0,0 +1,12 @@
#include "byteswap.h"
uint32_t swap_uint32(uint32_t val) {
val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF);
return (val << 16) | (val >> 16);
}
uint64_t swap_uint64(uint64_t val) {
val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL);
val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL);
return (val << 32) | (val >> 32);
}
@@ -0,0 +1,17 @@
#pragma once
#include <stdint.h>
/**
* @brief Swap bytes in 32-bit value
* @param val value to swap bytes in
* @return Value with bytes swapped
*/
uint32_t swap_uint32(uint32_t val);
/**
* @brief Swap bytes in 64-bit value
* @param val value to swap bytes in
* @return Value with bytes swapped
*/
uint64_t swap_uint64(uint64_t val);
@@ -0,0 +1,64 @@
#include <string.h>
#include "sha256.h"
#include "memxor.h"
#define IPAD 0x36
#define OPAD 0x5c
/* Concatenate two preprocessor tokens. */
#define _GLHMAC_CONCAT_(prefix, suffix) prefix##suffix
#define _GLHMAC_CONCAT(prefix, suffix) _GLHMAC_CONCAT_(prefix, suffix)
#if GL_HMAC_NAME == 5
#define HMAC_ALG md5
#else
#define HMAC_ALG _GLHMAC_CONCAT(sha, GL_HMAC_NAME)
#endif
#define GL_HMAC_CTX _GLHMAC_CONCAT(HMAC_ALG, _ctx)
#define GL_HMAC_FN _GLHMAC_CONCAT(hmac_, HMAC_ALG)
#define GL_HMAC_FN_INIT _GLHMAC_CONCAT(HMAC_ALG, _init_ctx)
#define GL_HMAC_FN_BLOC _GLHMAC_CONCAT(HMAC_ALG, _process_block)
#define GL_HMAC_FN_PROC _GLHMAC_CONCAT(HMAC_ALG, _process_bytes)
#define GL_HMAC_FN_FINI _GLHMAC_CONCAT(HMAC_ALG, _finish_ctx)
static void
hmac_hash(const void* key, size_t keylen, const void* in, size_t inlen, int pad, void* resbuf) {
struct GL_HMAC_CTX hmac_ctx;
char block[GL_HMAC_BLOCKSIZE];
memset(block, pad, sizeof block);
memxor(block, key, keylen);
GL_HMAC_FN_INIT(&hmac_ctx);
GL_HMAC_FN_BLOC(block, sizeof block, &hmac_ctx);
GL_HMAC_FN_PROC(in, inlen, &hmac_ctx);
GL_HMAC_FN_FINI(&hmac_ctx, resbuf);
}
int GL_HMAC_FN(const void* key, size_t keylen, const void* in, size_t inlen, void* resbuf) {
char optkeybuf[GL_HMAC_HASHSIZE];
char innerhash[GL_HMAC_HASHSIZE];
/* Ensure key size is <= block size. */
if(keylen > GL_HMAC_BLOCKSIZE) {
struct GL_HMAC_CTX keyhash;
GL_HMAC_FN_INIT(&keyhash);
GL_HMAC_FN_PROC(key, keylen, &keyhash);
GL_HMAC_FN_FINI(&keyhash, optkeybuf);
key = optkeybuf;
/* zero padding of the key to the block size
is implicit in the memxor. */
keylen = sizeof optkeybuf;
}
/* Compute INNERHASH from KEY and IN. */
hmac_hash(key, keylen, in, inlen, IPAD, innerhash);
/* Compute result from KEY and INNERHASH. */
hmac_hash(key, keylen, innerhash, sizeof innerhash, OPAD, resbuf);
return 0;
}
@@ -0,0 +1,24 @@
/* hmac-sha1.c -- hashed message authentication codes
Copyright (C) 2018-2022 Free Software Foundation, Inc.
This file is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
#include "hmac_sha1.h"
#include "sha1.h"
#define GL_HMAC_NAME 1
#define GL_HMAC_BLOCKSIZE 64
#define GL_HMAC_HASHSIZE 20
#include "hmac_common.h"
@@ -0,0 +1,11 @@
#pragma once
#include <stddef.h>
#define HMAC_SHA1_RESULT_SIZE 20
/* Compute Hashed Message Authentication Code with SHA-1, over BUFFER
data of BUFLEN bytes using the KEY of KEYLEN bytes, writing the
output to pre-allocated 20 byte minimum RESBUF buffer. Return 0 on
success. */
int hmac_sha1(const void* key, size_t keylen, const void* in, size_t inlen, void* restrict resbuf);
@@ -0,0 +1,23 @@
/* hmac-sha256.c -- hashed message authentication codes
Copyright (C) 2018-2022 Free Software Foundation, Inc.
This file is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
#include "hmac_sha256.h"
#define GL_HMAC_NAME 256
#define GL_HMAC_BLOCKSIZE 64
#define GL_HMAC_HASHSIZE 32
#include "hmac_common.h"
@@ -0,0 +1,11 @@
#pragma once
#include <stddef.h>
#define HMAC_SHA256_RESULT_SIZE 32
/* Compute Hashed Message Authentication Code with SHA-256, over BUFFER
data of BUFLEN bytes using the KEY of KEYLEN bytes, writing the
output to pre-allocated 32 byte minimum RESBUF buffer. Return 0 on
success. */
int hmac_sha256(const void* key, size_t keylen, const void* in, size_t inlen, void* restrict resbuf);
@@ -0,0 +1,24 @@
/* hmac-sha512.c -- hashed message authentication codes
Copyright (C) 2018-2022 Free Software Foundation, Inc.
This file is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
#include "hmac_sha512.h"
#include "sha512.h"
#define GL_HMAC_NAME 512
#define GL_HMAC_BLOCKSIZE 128
#define GL_HMAC_HASHSIZE 64
#include "hmac_common.h"
@@ -0,0 +1,11 @@
#pragma once
#include <stddef.h>
#define HMAC_SHA512_RESULT_SIZE 64
/* Compute Hashed Message Authentication Code with SHA-512, over BUFFER
data of BUFLEN bytes using the KEY of KEYLEN bytes, writing the
output to pre-allocated 64 byte minimum RESBUF buffer. Return 0 on
success. */
int hmac_sha512(const void* key, size_t keylen, const void* in, size_t inlen, void* restrict resbuf);
@@ -0,0 +1,30 @@
/* memxor.c -- perform binary exclusive OR operation of two memory blocks.
Copyright (C) 2005, 2006 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
/* Written by Simon Josefsson. The interface was inspired by memxor
in Niels Möller's Nettle. */
#include "memxor.h"
void* memxor(void* /*restrict*/ dest, const void* /*restrict*/ src, size_t n) {
char const* s = (char const*)src;
char* d = (char*)dest;
for(; n > 0; n--) *d++ ^= *s++;
return dest;
}
@@ -0,0 +1,28 @@
/* memxor.h -- perform binary exclusive OR operation on memory blocks.
Copyright (C) 2005 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
/* Written by Simon Josefsson. The interface was inspired by memxor
in Niels Möller's Nettle. */
#pragma once
#include <stddef.h>
/* Compute binary exclusive OR of memory areas DEST and SRC, putting
the result in DEST, of length N bytes. Returns a pointer to
DEST. */
void* memxor(void* /*restrict*/ dest, const void* /*restrict*/ src, size_t n);
@@ -0,0 +1,269 @@
/* sha1.c - Functions to compute SHA1 message digest of files or
memory blocks according to the NIST specification FIPS-180-1.
Copyright (C) 2000-2001, 2003-2006, 2008-2022 Free Software Foundation, Inc.
This file is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
/* Written by Scott G. Miller
Credits:
Robert Klep <robert@ilse.nl> -- Expansion function fix
*/
/* Specification. */
#include "sha1.h"
#include <stdint.h>
#include <string.h>
#ifdef WORDS_BIGENDIAN
#define SWAP(n) (n)
#else
#include "byteswap.h"
#define SWAP(n) swap_uint32(n)
#endif
/* This array contains the bytes used to pad the buffer to the next
64-byte boundary. (RFC 1321, 3.1: Step 1) */
static const unsigned char fillbuf[64] = {0x80, 0 /* , 0, 0, ... */};
/* Take a pointer to a 160 bit block of data (five 32 bit ints) and
initialize it to the start constants of the SHA1 algorithm. This
must be called before using hash in the call to sha1_hash. */
void sha1_init_ctx(struct sha1_ctx* ctx) {
ctx->A = 0x67452301;
ctx->B = 0xefcdab89;
ctx->C = 0x98badcfe;
ctx->D = 0x10325476;
ctx->E = 0xc3d2e1f0;
ctx->total[0] = ctx->total[1] = 0;
ctx->buflen = 0;
}
/* Copy the 4 byte value from v into the memory location pointed to by *cp,
If your architecture allows unaligned access this is equivalent to
* (uint32_t *) cp = v */
static void set_uint32(char* cp, uint32_t v) {
memcpy(cp, &v, sizeof v);
}
/* Put result from CTX in first 20 bytes following RESBUF. The result
must be in little endian byte order. */
void* sha1_read_ctx(const struct sha1_ctx* ctx, void* resbuf) {
char* r = resbuf;
set_uint32(r + 0 * sizeof ctx->A, SWAP(ctx->A));
set_uint32(r + 1 * sizeof ctx->B, SWAP(ctx->B));
set_uint32(r + 2 * sizeof ctx->C, SWAP(ctx->C));
set_uint32(r + 3 * sizeof ctx->D, SWAP(ctx->D));
set_uint32(r + 4 * sizeof ctx->E, SWAP(ctx->E));
return resbuf;
}
/* Process the remaining bytes in the internal buffer and the usual
prolog according to the standard and write the result to RESBUF. */
void* sha1_finish_ctx(struct sha1_ctx* ctx, void* resbuf) {
/* Take yet unprocessed bytes into account. */
uint32_t bytes = ctx->buflen;
size_t size = (bytes < 56) ? 64 / 4 : 64 * 2 / 4;
/* Now count remaining bytes. */
ctx->total[0] += bytes;
if(ctx->total[0] < bytes) ++ctx->total[1];
/* Put the 64-bit file length in *bits* at the end of the buffer. */
ctx->buffer[size - 2] = SWAP((ctx->total[1] << 3) | (ctx->total[0] >> 29));
ctx->buffer[size - 1] = SWAP(ctx->total[0] << 3);
memcpy(&((char*)ctx->buffer)[bytes], fillbuf, (size - 2) * 4 - bytes);
/* Process last bytes. */
sha1_process_block(ctx->buffer, size * 4, ctx);
return sha1_read_ctx(ctx, resbuf);
}
/* Compute SHA1 message digest for LEN bytes beginning at BUFFER. The
result is always in little endian byte order, so that a byte-wise
output yields to the wanted ASCII representation of the message
digest. */
void* sha1_buffer(const char* buffer, size_t len, void* resblock) {
struct sha1_ctx ctx;
/* Initialize the computation context. */
sha1_init_ctx(&ctx);
/* Process whole buffer but last len % 64 bytes. */
sha1_process_bytes(buffer, len, &ctx);
/* Put result in desired memory area. */
return sha1_finish_ctx(&ctx, resblock);
}
void sha1_process_bytes(const void* buffer, size_t len, struct sha1_ctx* ctx) {
/* When we already have some bits in our internal buffer concatenate
both inputs first. */
if(ctx->buflen != 0) {
size_t left_over = ctx->buflen;
size_t add = 128 - left_over > len ? len : 128 - left_over;
memcpy(&((char*)ctx->buffer)[left_over], buffer, add);
ctx->buflen += add;
if(ctx->buflen > 64) {
sha1_process_block(ctx->buffer, ctx->buflen & ~63, ctx);
ctx->buflen &= 63;
/* The regions in the following copy operation cannot overlap,
because ctx->buflen < 64 ≤ (left_over + add) & ~63. */
memcpy(ctx->buffer, &((char*)ctx->buffer)[(left_over + add) & ~63], ctx->buflen);
}
buffer = (const char*)buffer + add;
len -= add;
}
/* Process available complete blocks. */
if(len >= 64) {
#if !(_STRING_ARCH_unaligned || _STRING_INLINE_unaligned)
#define UNALIGNED_P(p) ((uintptr_t)(p) % sizeof(uint32_t) != 0)
if(UNALIGNED_P(buffer))
while(len > 64) {
sha1_process_block(memcpy(ctx->buffer, buffer, 64), 64, ctx); //-V1086
buffer = (const char*)buffer + 64;
len -= 64;
}
else
#endif
{
sha1_process_block(buffer, len & ~63, ctx);
buffer = (const char*)buffer + (len & ~63);
len &= 63;
}
}
/* Move remaining bytes in internal buffer. */
if(len > 0) {
size_t left_over = ctx->buflen;
memcpy(&((char*)ctx->buffer)[left_over], buffer, len);
left_over += len;
if(left_over >= 64) {
sha1_process_block(ctx->buffer, 64, ctx);
left_over -= 64;
/* The regions in the following copy operation cannot overlap,
because left_over ≤ 64. */
memcpy(ctx->buffer, &ctx->buffer[16], left_over);
}
ctx->buflen = left_over;
}
}
/* --- Code below is the primary difference between md5.c and sha1.c --- */
/* SHA1 round constants */
#define K1 0x5a827999
#define K2 0x6ed9eba1
#define K3 0x8f1bbcdc
#define K4 0xca62c1d6
/* Round functions. Note that F2 is the same as F4. */
#define F1(B, C, D) (D ^ (B & (C ^ D)))
#define F2(B, C, D) (B ^ C ^ D)
#define F3(B, C, D) ((B & C) | (D & (B | C)))
#define F4(B, C, D) (B ^ C ^ D)
/* Process LEN bytes of BUFFER, accumulating context into CTX.
It is assumed that LEN % 64 == 0.
Most of this code comes from GnuPG's cipher/sha1.c. */
void sha1_process_block(const void* buffer, size_t len, struct sha1_ctx* ctx) {
const uint32_t* words = buffer;
size_t nwords = len / sizeof(uint32_t);
const uint32_t* endp = words + nwords;
uint32_t x[16];
uint32_t a = ctx->A;
uint32_t b = ctx->B;
uint32_t c = ctx->C;
uint32_t d = ctx->D;
uint32_t e = ctx->E;
uint32_t lolen = len;
/* First increment the byte count. RFC 1321 specifies the possible
length of the file up to 2^64 bits. Here we only compute the
number of bytes. Do a double word increment. */
ctx->total[0] += lolen;
ctx->total[1] += (len >> 31 >> 1) + (ctx->total[0] < lolen);
#define rol(x, n) (((x) << (n)) | ((uint32_t)(x) >> (32 - (n))))
#define M(I) \
(tm = x[I & 0x0f] ^ x[(I - 14) & 0x0f] ^ x[(I - 8) & 0x0f] ^ x[(I - 3) & 0x0f], \
(x[I & 0x0f] = rol(tm, 1)))
#define R(A, B, C, D, E, F, K, M) \
do { \
E += rol(A, 5) + F(B, C, D) + K + M; \
B = rol(B, 30); \
} while(0)
while(words < endp) {
uint32_t tm;
int t;
for(t = 0; t < 16; t++) {
x[t] = SWAP(*words);
words++;
}
for(int i = 0; i < 80; i++) {
uint32_t xx = i < 16 ? x[i] : M(i);
uint32_t ki = i / 20;
switch(ki) {
case 0:
R(a, b, c, d, e, F1, K1, xx);
break;
case 1:
R(a, b, c, d, e, F2, K2, xx);
break;
case 2:
R(a, b, c, d, e, F3, K3, xx);
break;
default:
R(a, b, c, d, e, F4, K4, xx);
break;
}
uint32_t tt = a;
a = e;
e = d;
d = c;
c = b;
b = tt;
}
a = ctx->A += a;
b = ctx->B += b;
c = ctx->C += c;
d = ctx->D += d;
e = ctx->E += e;
}
}
/*
* Hey Emacs!
* Local Variables:
* coding: utf-8
* End:
*/
@@ -0,0 +1,84 @@
/* Declarations of functions and data types used for SHA1 sum
library functions.
Copyright (C) 2000-2001, 2003, 2005-2006, 2008-2022 Free Software
Foundation, Inc.
This file is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
#pragma once
#include <stdio.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define SHA1_DIGEST_SIZE 20
/* Structure to save state of computation between the single steps. */
struct sha1_ctx {
uint32_t A;
uint32_t B;
uint32_t C;
uint32_t D;
uint32_t E;
uint32_t total[2];
uint32_t buflen; /* ≥ 0, ≤ 128 */
uint32_t buffer[32]; /* 128 bytes; the first buflen bytes are in use */
};
/* Initialize structure containing state of computation. */
extern void sha1_init_ctx(struct sha1_ctx* ctx);
/* Starting with the result of former calls of this function (or the
initialization function update the context for the next LEN bytes
starting at BUFFER.
It is necessary that LEN is a multiple of 64!!! */
extern void sha1_process_block(const void* buffer, size_t len, struct sha1_ctx* ctx);
/* Starting with the result of former calls of this function (or the
initialization function update the context for the next LEN bytes
starting at BUFFER.
It is NOT required that LEN is a multiple of 64. */
extern void sha1_process_bytes(const void* buffer, size_t len, struct sha1_ctx* ctx);
/* Process the remaining bytes in the buffer and put result from CTX
in first 20 bytes following RESBUF. The result is always in little
endian byte order, so that a byte-wise output yields to the wanted
ASCII representation of the message digest. */
extern void* sha1_finish_ctx(struct sha1_ctx* ctx, void* restrict resbuf);
/* Put result from CTX in first 20 bytes following RESBUF. The result is
always in little endian byte order, so that a byte-wise output yields
to the wanted ASCII representation of the message digest. */
extern void* sha1_read_ctx(const struct sha1_ctx* ctx, void* restrict resbuf);
/* Compute SHA1 message digest for LEN bytes beginning at BUFFER. The
result is always in little endian byte order, so that a byte-wise
output yields to the wanted ASCII representation of the message
digest. */
extern void* sha1_buffer(const char* buffer, size_t len, void* restrict resblock);
#ifdef __cplusplus
}
#endif
/*
* Hey Emacs!
* Local Variables:
* coding: utf-8
* End:
*/
@@ -0,0 +1,286 @@
/* sha256.c - Functions to compute SHA256 message digest of files or
memory blocks according to the NIST specification FIPS-180-2.
Copyright (C) 2005-2006, 2008-2022 Free Software Foundation, Inc.
This file is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
/* Written by David Madore, considerably copypasting from
Scott G. Miller's sha1.c
*/
/* Specification. */
#include "sha256.h"
#include <stdint.h>
#include <string.h>
#ifdef WORDS_BIGENDIAN
#define SWAP(n) (n)
#else
#include "byteswap.h"
#define SWAP(n) swap_uint32(n)
#endif
/* This array contains the bytes used to pad the buffer to the next
64-byte boundary. */
static const unsigned char fillbuf[64] = {0x80, 0 /* , 0, 0, ... */};
/*
Takes a pointer to a 256 bit block of data (eight 32 bit ints) and
initializes it to the start constants of the SHA256 algorithm. This
must be called before using hash in the call to sha256_hash
*/
void sha256_init_ctx(struct sha256_ctx* ctx) {
ctx->state[0] = 0x6a09e667UL;
ctx->state[1] = 0xbb67ae85UL;
ctx->state[2] = 0x3c6ef372UL;
ctx->state[3] = 0xa54ff53aUL;
ctx->state[4] = 0x510e527fUL;
ctx->state[5] = 0x9b05688cUL;
ctx->state[6] = 0x1f83d9abUL;
ctx->state[7] = 0x5be0cd19UL;
ctx->total[0] = ctx->total[1] = 0;
ctx->buflen = 0;
}
/* Copy the value from v into the memory location pointed to by *CP,
If your architecture allows unaligned access, this is equivalent to
* (__typeof__ (v) *) cp = v */
static void set_uint32(char* cp, uint32_t v) {
memcpy(cp, &v, sizeof v);
}
/* Put result from CTX in first 32 bytes following RESBUF.
The result must be in little endian byte order. */
void* sha256_read_ctx(const struct sha256_ctx* ctx, void* resbuf) {
int i;
char* r = resbuf;
for(i = 0; i < 8; i++) set_uint32(r + i * sizeof ctx->state[0], SWAP(ctx->state[i]));
return resbuf;
}
/* Process the remaining bytes in the internal buffer and the usual
prolog according to the standard and write the result to RESBUF. */
static void sha256_conclude_ctx(struct sha256_ctx* ctx) {
/* Take yet unprocessed bytes into account. */
size_t bytes = ctx->buflen;
size_t size = (bytes < 56) ? 64 / 4 : 64 * 2 / 4;
/* Now count remaining bytes. */
ctx->total[0] += bytes;
if(ctx->total[0] < bytes) ++ctx->total[1];
/* Put the 64-bit file length in *bits* at the end of the buffer.
Use set_uint32 rather than a simple assignment, to avoid risk of
unaligned access. */
set_uint32((char*)&ctx->buffer[size - 2], SWAP((ctx->total[1] << 3) | (ctx->total[0] >> 29)));
set_uint32((char*)&ctx->buffer[size - 1], SWAP(ctx->total[0] << 3));
memcpy(&((char*)ctx->buffer)[bytes], fillbuf, (size - 2) * 4 - bytes);
/* Process last bytes. */
sha256_process_block(ctx->buffer, size * 4, ctx);
}
void* sha256_finish_ctx(struct sha256_ctx* ctx, void* resbuf) {
sha256_conclude_ctx(ctx);
return sha256_read_ctx(ctx, resbuf);
}
/* Compute SHA256 message digest for LEN bytes beginning at BUFFER. The
result is always in little endian byte order, so that a byte-wise
output yields to the wanted ASCII representation of the message
digest. */
void* sha256_buffer(const char* buffer, size_t len, void* resblock) {
struct sha256_ctx ctx;
/* Initialize the computation context. */
sha256_init_ctx(&ctx);
/* Process whole buffer but last len % 64 bytes. */
sha256_process_bytes(buffer, len, &ctx);
/* Put result in desired memory area. */
return sha256_finish_ctx(&ctx, resblock);
}
void sha256_process_bytes(const void* buffer, size_t len, struct sha256_ctx* ctx) {
/* When we already have some bits in our internal buffer concatenate
both inputs first. */
if(ctx->buflen != 0) {
size_t left_over = ctx->buflen;
size_t add = 128 - left_over > len ? len : 128 - left_over;
memcpy(&((char*)ctx->buffer)[left_over], buffer, add);
ctx->buflen += add;
if(ctx->buflen > 64) {
sha256_process_block(ctx->buffer, ctx->buflen & ~63, ctx);
ctx->buflen &= 63;
/* The regions in the following copy operation cannot overlap,
because ctx->buflen < 64 ≤ (left_over + add) & ~63. */
memcpy(ctx->buffer, &((char*)ctx->buffer)[(left_over + add) & ~63], ctx->buflen);
}
buffer = (const char*)buffer + add;
len -= add;
}
/* Process available complete blocks. */
if(len >= 64) {
#if !(_STRING_ARCH_unaligned || _STRING_INLINE_unaligned)
#define UNALIGNED_P(p) ((uintptr_t)(p) % sizeof(uint32_t) != 0)
if(UNALIGNED_P(buffer))
while(len > 64) {
sha256_process_block(memcpy(ctx->buffer, buffer, 64), 64, ctx); //-V1086
buffer = (const char*)buffer + 64;
len -= 64;
}
else
#endif
{
sha256_process_block(buffer, len & ~63, ctx);
buffer = (const char*)buffer + (len & ~63);
len &= 63;
}
}
/* Move remaining bytes in internal buffer. */
if(len > 0) {
size_t left_over = ctx->buflen;
memcpy(&((char*)ctx->buffer)[left_over], buffer, len);
left_over += len;
if(left_over >= 64) {
sha256_process_block(ctx->buffer, 64, ctx);
left_over -= 64;
/* The regions in the following copy operation cannot overlap,
because left_over ≤ 64. */
memcpy(ctx->buffer, &ctx->buffer[16], left_over);
}
ctx->buflen = left_over;
}
}
/* --- Code below is the primary difference between sha1.c and sha256.c --- */
/* SHA256 round constants */
#define K(I) sha256_round_constants[I]
static const uint32_t sha256_round_constants[64] = {
0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL, 0x59f111f1UL,
0x923f82a4UL, 0xab1c5ed5UL, 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL,
0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL, 0xe49b69c1UL, 0xefbe4786UL,
0x0fc19dc6UL, 0x240ca1ccUL, 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL,
0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL,
0x06ca6351UL, 0x14292967UL, 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL,
0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, 0xa2bfe8a1UL, 0xa81a664bUL,
0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL,
0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL,
0x5b9cca4fUL, 0x682e6ff3UL, 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL,
0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL,
};
/* Round functions. */
#define F2(A, B, C) ((A & B) | (C & (A | B)))
#define F1(E, F, G) (G ^ (E & (F ^ G)))
/* Process LEN bytes of BUFFER, accumulating context into CTX.
It is assumed that LEN % 64 == 0.
Most of this code comes from GnuPG's cipher/sha1.c. */
void sha256_process_block(const void* buffer, size_t len, struct sha256_ctx* ctx) {
const uint32_t* words = buffer;
size_t nwords = len / sizeof(uint32_t);
const uint32_t* endp = words + nwords;
uint32_t x[16];
uint32_t a = ctx->state[0];
uint32_t b = ctx->state[1];
uint32_t c = ctx->state[2];
uint32_t d = ctx->state[3];
uint32_t e = ctx->state[4];
uint32_t f = ctx->state[5];
uint32_t g = ctx->state[6];
uint32_t h = ctx->state[7];
uint32_t lolen = len;
/* First increment the byte count. FIPS PUB 180-2 specifies the possible
length of the file up to 2^64 bits. Here we only compute the
number of bytes. Do a double word increment. */
ctx->total[0] += lolen;
ctx->total[1] += (len >> 31 >> 1) + (ctx->total[0] < lolen);
#define rol(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
#define S0(x) (rol(x, 25) ^ rol(x, 14) ^ (x >> 3))
#define S1(x) (rol(x, 15) ^ rol(x, 13) ^ (x >> 10))
#define SS0(x) (rol(x, 30) ^ rol(x, 19) ^ rol(x, 10))
#define SS1(x) (rol(x, 26) ^ rol(x, 21) ^ rol(x, 7))
#define M(I) \
(tm = S1(x[(I - 2) & 0x0f]) + x[(I - 7) & 0x0f] + S0(x[(I - 15) & 0x0f]) + x[I & 0x0f], \
x[I & 0x0f] = tm)
#define R(A, B, C, D, E, F, G, H, K, M) \
do { \
t0 = SS0(A) + F2(A, B, C); \
t1 = H + SS1(E) + F1(E, F, G) + K + M; \
D += t1; \
H = t0 + t1; \
} while(0)
while(words < endp) {
uint32_t tm;
uint32_t t0, t1;
int t;
/* FIXME: see sha1.c for a better implementation. */
for(t = 0; t < 16; t++) {
x[t] = SWAP(*words);
words++;
}
for(int i = 0; i < 64; i++) {
uint32_t xx = i < 16 ? x[i] : M(i);
R(a, b, c, d, e, f, g, h, K(i), xx);
uint32_t tt = a;
a = h;
h = g;
g = f;
f = e;
e = d;
d = c;
c = b;
b = tt;
}
a = ctx->state[0] += a;
b = ctx->state[1] += b;
c = ctx->state[2] += c;
d = ctx->state[3] += d;
e = ctx->state[4] += e;
f = ctx->state[5] += f;
g = ctx->state[6] += g;
h = ctx->state[7] += h;
}
}
/*
* Hey Emacs!
* Local Variables:
* coding: utf-8
* End:
*/
@@ -0,0 +1,79 @@
/* Declarations of functions and data types used for SHA256 sum
library functions.
Copyright (C) 2005-2006, 2008-2022 Free Software Foundation, Inc.
This file is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
#pragma once
#include <stdio.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
enum { SHA256_DIGEST_SIZE = 256 / 8 };
/* Structure to save state of computation between the single steps. */
struct sha256_ctx {
uint32_t state[8];
uint32_t total[2];
size_t buflen; /* ≥ 0, ≤ 128 */
uint32_t buffer[32]; /* 128 bytes; the first buflen bytes are in use */
};
/* Initialize structure containing state of computation. */
extern void sha256_init_ctx(struct sha256_ctx* ctx);
/* Starting with the result of former calls of this function (or the
initialization function update the context for the next LEN bytes
starting at BUFFER.
It is necessary that LEN is a multiple of 64!!! */
extern void sha256_process_block(const void* buffer, size_t len, struct sha256_ctx* ctx);
/* Starting with the result of former calls of this function (or the
initialization function update the context for the next LEN bytes
starting at BUFFER.
It is NOT required that LEN is a multiple of 64. */
extern void sha256_process_bytes(const void* buffer, size_t len, struct sha256_ctx* ctx);
/* Process the remaining bytes in the buffer and put result from CTX
in first 32 (28) bytes following RESBUF. The result is always in little
endian byte order, so that a byte-wise output yields to the wanted
ASCII representation of the message digest. */
extern void* sha256_finish_ctx(struct sha256_ctx* ctx, void* restrict resbuf);
/* Put result from CTX in first 32 (28) bytes following RESBUF. The result is
always in little endian byte order, so that a byte-wise output yields
to the wanted ASCII representation of the message digest. */
extern void* sha256_read_ctx(const struct sha256_ctx* ctx, void* restrict resbuf);
/* Compute SHA256 message digest for LEN bytes beginning at BUFFER.
The result is always in little endian byte order, so that a byte-wise
output yields to the wanted ASCII representation of the message
digest. */
extern void* sha256_buffer(const char* buffer, size_t len, void* restrict resblock);
#ifdef __cplusplus
}
#endif
/*
* Hey Emacs!
* Local Variables:
* coding: utf-8
* End:
*/
@@ -0,0 +1,315 @@
/* sha512.c - Functions to compute SHA512 message digest of files or
memory blocks according to the NIST specification FIPS-180-2.
Copyright (C) 2005-2006, 2008-2022 Free Software Foundation, Inc.
This file is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
/* Written by David Madore, considerably copypasting from
Scott G. Miller's sha1.c
*/
/* Specification. */
#include "sha512.h"
#include <stdint.h>
#include <string.h>
#ifdef WORDS_BIGENDIAN
#define SWAP(n) (n)
#else
#include "byteswap.h"
#define SWAP(n) swap_uint64(n)
#endif
/* This array contains the bytes used to pad the buffer to the next
128-byte boundary. */
static const unsigned char fillbuf[128] = {0x80, 0 /* , 0, 0, ... */};
/*
Takes a pointer to a 512 bit block of data (eight 64 bit ints) and
initializes it to the start constants of the SHA512 algorithm. This
must be called before using hash in the call to sha512_hash
*/
void sha512_init_ctx(struct sha512_ctx* ctx) {
ctx->state[0] = u64hilo(0x6a09e667, 0xf3bcc908);
ctx->state[1] = u64hilo(0xbb67ae85, 0x84caa73b);
ctx->state[2] = u64hilo(0x3c6ef372, 0xfe94f82b);
ctx->state[3] = u64hilo(0xa54ff53a, 0x5f1d36f1);
ctx->state[4] = u64hilo(0x510e527f, 0xade682d1);
ctx->state[5] = u64hilo(0x9b05688c, 0x2b3e6c1f);
ctx->state[6] = u64hilo(0x1f83d9ab, 0xfb41bd6b);
ctx->state[7] = u64hilo(0x5be0cd19, 0x137e2179);
ctx->total[0] = ctx->total[1] = u64lo(0);
ctx->buflen = 0;
}
/* Copy the value from V into the memory location pointed to by *CP,
If your architecture allows unaligned access, this is equivalent to
* (__typeof__ (v) *) cp = v */
static void set_uint64(char* cp, u64 v) {
memcpy(cp, &v, sizeof v);
}
/* Put result from CTX in first 64 bytes following RESBUF.
The result must be in little endian byte order. */
void* sha512_read_ctx(const struct sha512_ctx* ctx, void* resbuf) {
int i;
char* r = resbuf;
for(i = 0; i < 8; i++) set_uint64(r + i * sizeof ctx->state[0], SWAP(ctx->state[i]));
return resbuf;
}
/* Process the remaining bytes in the internal buffer and the usual
prolog according to the standard and write the result to RESBUF. */
static void sha512_conclude_ctx(struct sha512_ctx* ctx) {
/* Take yet unprocessed bytes into account. */
size_t bytes = ctx->buflen;
size_t size = (bytes < 112) ? 128 / 8 : 128 * 2 / 8;
/* Now count remaining bytes. */
ctx->total[0] = u64plus(ctx->total[0], u64lo(bytes));
if(u64lt(ctx->total[0], u64lo(bytes))) ctx->total[1] = u64plus(ctx->total[1], u64lo(1));
/* Put the 128-bit file length in *bits* at the end of the buffer.
Use set_uint64 rather than a simple assignment, to avoid risk of
unaligned access. */
set_uint64(
(char*)&ctx->buffer[size - 2],
SWAP(u64or(u64shl(ctx->total[1], 3), u64shr(ctx->total[0], 61))));
set_uint64((char*)&ctx->buffer[size - 1], SWAP(u64shl(ctx->total[0], 3)));
memcpy(&((char*)ctx->buffer)[bytes], fillbuf, (size - 2) * 8 - bytes);
/* Process last bytes. */
sha512_process_block(ctx->buffer, size * 8, ctx);
}
void* sha512_finish_ctx(struct sha512_ctx* ctx, void* resbuf) {
sha512_conclude_ctx(ctx);
return sha512_read_ctx(ctx, resbuf);
}
/* Compute SHA512 message digest for LEN bytes beginning at BUFFER. The
result is always in little endian byte order, so that a byte-wise
output yields to the wanted ASCII representation of the message
digest. */
void* sha512_buffer(const char* buffer, size_t len, void* resblock) {
struct sha512_ctx ctx;
/* Initialize the computation context. */
sha512_init_ctx(&ctx);
/* Process whole buffer but last len % 128 bytes. */
sha512_process_bytes(buffer, len, &ctx);
/* Put result in desired memory area. */
return sha512_finish_ctx(&ctx, resblock);
}
void sha512_process_bytes(const void* buffer, size_t len, struct sha512_ctx* ctx) {
/* When we already have some bits in our internal buffer concatenate
both inputs first. */
if(ctx->buflen != 0) {
size_t left_over = ctx->buflen;
size_t add = 256 - left_over > len ? len : 256 - left_over;
memcpy(&((char*)ctx->buffer)[left_over], buffer, add);
ctx->buflen += add;
if(ctx->buflen > 128) {
sha512_process_block(ctx->buffer, ctx->buflen & ~127, ctx);
ctx->buflen &= 127;
/* The regions in the following copy operation cannot overlap,
because ctx->buflen < 128 ≤ (left_over + add) & ~127. */
memcpy(ctx->buffer, &((char*)ctx->buffer)[(left_over + add) & ~127], ctx->buflen);
}
buffer = (const char*)buffer + add;
len -= add;
}
/* Process available complete blocks. */
if(len >= 128) {
#if !(_STRING_ARCH_unaligned || _STRING_INLINE_unaligned)
#define UNALIGNED_P(p) ((uintptr_t)(p) % sizeof(u64) != 0)
if(UNALIGNED_P(buffer))
while(len > 128) {
sha512_process_block(memcpy(ctx->buffer, buffer, 128), 128, ctx); //-V1086
buffer = (const char*)buffer + 128;
len -= 128;
}
else
#endif
{
sha512_process_block(buffer, len & ~127, ctx);
buffer = (const char*)buffer + (len & ~127);
len &= 127;
}
}
/* Move remaining bytes in internal buffer. */
if(len > 0) {
size_t left_over = ctx->buflen;
memcpy(&((char*)ctx->buffer)[left_over], buffer, len);
left_over += len;
if(left_over >= 128) {
sha512_process_block(ctx->buffer, 128, ctx);
left_over -= 128;
/* The regions in the following copy operation cannot overlap,
because left_over ≤ 128. */
memcpy(ctx->buffer, &ctx->buffer[16], left_over);
}
ctx->buflen = left_over;
}
}
/* --- Code below is the primary difference between sha1.c and sha512.c --- */
/* SHA512 round constants */
#define K(I) sha512_round_constants[I]
static u64 const sha512_round_constants[80] = {
u64init(0x428a2f98, 0xd728ae22), u64init(0x71374491, 0x23ef65cd),
u64init(0xb5c0fbcf, 0xec4d3b2f), u64init(0xe9b5dba5, 0x8189dbbc),
u64init(0x3956c25b, 0xf348b538), u64init(0x59f111f1, 0xb605d019),
u64init(0x923f82a4, 0xaf194f9b), u64init(0xab1c5ed5, 0xda6d8118),
u64init(0xd807aa98, 0xa3030242), u64init(0x12835b01, 0x45706fbe),
u64init(0x243185be, 0x4ee4b28c), u64init(0x550c7dc3, 0xd5ffb4e2),
u64init(0x72be5d74, 0xf27b896f), u64init(0x80deb1fe, 0x3b1696b1),
u64init(0x9bdc06a7, 0x25c71235), u64init(0xc19bf174, 0xcf692694),
u64init(0xe49b69c1, 0x9ef14ad2), u64init(0xefbe4786, 0x384f25e3),
u64init(0x0fc19dc6, 0x8b8cd5b5), u64init(0x240ca1cc, 0x77ac9c65),
u64init(0x2de92c6f, 0x592b0275), u64init(0x4a7484aa, 0x6ea6e483),
u64init(0x5cb0a9dc, 0xbd41fbd4), u64init(0x76f988da, 0x831153b5),
u64init(0x983e5152, 0xee66dfab), u64init(0xa831c66d, 0x2db43210),
u64init(0xb00327c8, 0x98fb213f), u64init(0xbf597fc7, 0xbeef0ee4),
u64init(0xc6e00bf3, 0x3da88fc2), u64init(0xd5a79147, 0x930aa725),
u64init(0x06ca6351, 0xe003826f), u64init(0x14292967, 0x0a0e6e70),
u64init(0x27b70a85, 0x46d22ffc), u64init(0x2e1b2138, 0x5c26c926),
u64init(0x4d2c6dfc, 0x5ac42aed), u64init(0x53380d13, 0x9d95b3df),
u64init(0x650a7354, 0x8baf63de), u64init(0x766a0abb, 0x3c77b2a8),
u64init(0x81c2c92e, 0x47edaee6), u64init(0x92722c85, 0x1482353b),
u64init(0xa2bfe8a1, 0x4cf10364), u64init(0xa81a664b, 0xbc423001),
u64init(0xc24b8b70, 0xd0f89791), u64init(0xc76c51a3, 0x0654be30),
u64init(0xd192e819, 0xd6ef5218), u64init(0xd6990624, 0x5565a910),
u64init(0xf40e3585, 0x5771202a), u64init(0x106aa070, 0x32bbd1b8),
u64init(0x19a4c116, 0xb8d2d0c8), u64init(0x1e376c08, 0x5141ab53),
u64init(0x2748774c, 0xdf8eeb99), u64init(0x34b0bcb5, 0xe19b48a8),
u64init(0x391c0cb3, 0xc5c95a63), u64init(0x4ed8aa4a, 0xe3418acb),
u64init(0x5b9cca4f, 0x7763e373), u64init(0x682e6ff3, 0xd6b2b8a3),
u64init(0x748f82ee, 0x5defb2fc), u64init(0x78a5636f, 0x43172f60),
u64init(0x84c87814, 0xa1f0ab72), u64init(0x8cc70208, 0x1a6439ec),
u64init(0x90befffa, 0x23631e28), u64init(0xa4506ceb, 0xde82bde9),
u64init(0xbef9a3f7, 0xb2c67915), u64init(0xc67178f2, 0xe372532b),
u64init(0xca273ece, 0xea26619c), u64init(0xd186b8c7, 0x21c0c207),
u64init(0xeada7dd6, 0xcde0eb1e), u64init(0xf57d4f7f, 0xee6ed178),
u64init(0x06f067aa, 0x72176fba), u64init(0x0a637dc5, 0xa2c898a6),
u64init(0x113f9804, 0xbef90dae), u64init(0x1b710b35, 0x131c471b),
u64init(0x28db77f5, 0x23047d84), u64init(0x32caab7b, 0x40c72493),
u64init(0x3c9ebe0a, 0x15c9bebc), u64init(0x431d67c4, 0x9c100d4c),
u64init(0x4cc5d4be, 0xcb3e42b6), u64init(0x597f299c, 0xfc657e2a),
u64init(0x5fcb6fab, 0x3ad6faec), u64init(0x6c44198c, 0x4a475817),
};
/* Round functions. */
#define F2(A, B, C) u64or(u64and(A, B), u64and(C, u64or(A, B)))
#define F1(E, F, G) u64xor(G, u64and(E, u64xor(F, G)))
/* Process LEN bytes of BUFFER, accumulating context into CTX.
It is assumed that LEN % 128 == 0.
Most of this code comes from GnuPG's cipher/sha1.c. */
void sha512_process_block(const void* buffer, size_t len, struct sha512_ctx* ctx) {
u64 const* words = buffer;
u64 const* endp = words + len / sizeof(u64);
u64 x[16];
u64 a = ctx->state[0];
u64 b = ctx->state[1];
u64 c = ctx->state[2];
u64 d = ctx->state[3];
u64 e = ctx->state[4];
u64 f = ctx->state[5];
u64 g = ctx->state[6];
u64 h = ctx->state[7];
u64 lolen = u64size(len);
/* First increment the byte count. FIPS PUB 180-2 specifies the possible
length of the file up to 2^128 bits. Here we only compute the
number of bytes. Do a double word increment. */
ctx->total[0] = u64plus(ctx->total[0], lolen);
ctx->total[1] = u64plus(
ctx->total[1], u64plus(u64size(len >> 31 >> 31 >> 2), u64lo(u64lt(ctx->total[0], lolen))));
#define S0(x) u64xor(u64rol(x, 63), u64xor(u64rol(x, 56), u64shr(x, 7)))
#define S1(x) u64xor(u64rol(x, 45), u64xor(u64rol(x, 3), u64shr(x, 6)))
#define SS0(x) u64xor(u64rol(x, 36), u64xor(u64rol(x, 30), u64rol(x, 25)))
#define SS1(x) u64xor(u64rol(x, 50), u64xor(u64rol(x, 46), u64rol(x, 23)))
#define M(I) \
(x[(I)&15] = u64plus( \
x[(I)&15], \
u64plus(S1(x[((I)-2) & 15]), u64plus(x[((I)-7) & 15], S0(x[((I)-15) & 15])))))
#define R(A, B, C, D, E, F, G, H, K, M) \
do { \
u64 t0 = u64plus(SS0(A), F2(A, B, C)); \
u64 t1 = u64plus(H, u64plus(SS1(E), u64plus(F1(E, F, G), u64plus(K, M)))); \
D = u64plus(D, t1); \
H = u64plus(t0, t1); \
} while(0)
while(words < endp) {
int t;
/* FIXME: see sha1.c for a better implementation. */
for(t = 0; t < 16; t++) {
x[t] = SWAP(*words);
words++;
}
for(int i = 0; i < 80; i++) {
u64 xx = i < 16 ? x[i] : M(i);
R(a, b, c, d, e, f, g, h, K(i), xx);
u64 tt = a;
a = h;
h = g;
g = f;
f = e;
e = d;
d = c;
c = b;
b = tt;
}
a = ctx->state[0] = u64plus(ctx->state[0], a);
b = ctx->state[1] = u64plus(ctx->state[1], b);
c = ctx->state[2] = u64plus(ctx->state[2], c);
d = ctx->state[3] = u64plus(ctx->state[3], d);
e = ctx->state[4] = u64plus(ctx->state[4], e);
f = ctx->state[5] = u64plus(ctx->state[5], f);
g = ctx->state[6] = u64plus(ctx->state[6], g);
h = ctx->state[7] = u64plus(ctx->state[7], h);
}
}
/*
* Hey Emacs!
* Local Variables:
* coding: utf-8
* End:
*/
@@ -0,0 +1,82 @@
/* Declarations of functions and data types used for SHA512 and SHA384 sum
library functions.
Copyright (C) 2005-2006, 2008-2022 Free Software Foundation, Inc.
This file is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
#pragma once
#include <stdio.h>
#include "u64.h"
#ifdef __cplusplus
extern "C" {
#endif
enum { SHA512_DIGEST_SIZE = 512 / 8 };
/* Structure to save state of computation between the single steps. */
struct sha512_ctx {
u64 state[8];
u64 total[2];
size_t buflen; /* ≥ 0, ≤ 256 */
u64 buffer[32]; /* 256 bytes; the first buflen bytes are in use */
};
/* Initialize structure containing state of computation. */
extern void sha512_init_ctx(struct sha512_ctx* ctx);
/* Starting with the result of former calls of this function (or the
initialization function update the context for the next LEN bytes
starting at BUFFER.
It is necessary that LEN is a multiple of 128!!! */
extern void sha512_process_block(const void* buffer, size_t len, struct sha512_ctx* ctx);
/* Starting with the result of former calls of this function (or the
initialization function update the context for the next LEN bytes
starting at BUFFER.
It is NOT required that LEN is a multiple of 128. */
extern void sha512_process_bytes(const void* buffer, size_t len, struct sha512_ctx* ctx);
/* Process the remaining bytes in the buffer and put result from CTX
in first 64 (48) bytes following RESBUF. The result is always in little
endian byte order, so that a byte-wise output yields to the wanted
ASCII representation of the message digest. */
extern void* sha512_finish_ctx(struct sha512_ctx* ctx, void* restrict resbuf);
/* Put result from CTX in first 64 (48) bytes following RESBUF. The result is
always in little endian byte order, so that a byte-wise output yields
to the wanted ASCII representation of the message digest.
IMPORTANT: On some systems it is required that RESBUF is correctly
aligned for a 32 bits value. */
extern void* sha512_read_ctx(const struct sha512_ctx* ctx, void* restrict resbuf);
/* Compute SHA512 message digest for LEN bytes beginning at BUFFER.
The result is always in little endian byte order, so that a byte-wise
output yields to the wanted ASCII representation of the message
digest. */
extern void* sha512_buffer(const char* buffer, size_t len, void* restrict resblock);
#ifdef __cplusplus
}
#endif
/*
* Hey Emacs!
* Local Variables:
* coding: utf-8
* End:
*/
@@ -0,0 +1,44 @@
/* uint64_t-like operations that work even on hosts lacking uint64_t
Copyright (C) 2006, 2009-2022 Free Software Foundation, Inc.
This file is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This file is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
/* Written by Paul Eggert. */
#pragma once
#include <stdint.h>
#ifndef _GL_U64_INLINE
#define _GL_U64_INLINE _GL_INLINE
#endif
/* Return X rotated left by N bits, where 0 < N < 64. */
#define u64rol(x, n) u64or(u64shl(x, n), u64shr(x, 64 - (n)))
/* Native implementations are trivial. See below for comments on what
these operations do. */
typedef uint64_t u64;
#define u64hilo(hi, lo) ((u64)(((u64)(hi) << 32) + (lo)))
#define u64init(hi, lo) u64hilo(hi, lo)
#define u64lo(x) ((u64)(x))
#define u64size(x) u64lo(x)
#define u64lt(x, y) ((x) < (y))
#define u64and(x, y) ((x) & (y))
#define u64or(x, y) ((x) | (y))
#define u64xor(x, y) ((x) ^ (y))
#define u64plus(x, y) ((x) + (y))
#define u64shl(x, n) ((x) << (n))
#define u64shr(x, n) ((x) >> (n))
@@ -0,0 +1,113 @@
#include "totp.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include "../hmac/hmac_sha1.h"
#include "../hmac/hmac_sha256.h"
#include "../hmac/hmac_sha512.h"
#include "../hmac/byteswap.h"
#include "../../lib/timezone_utils/timezone_utils.h"
#define HMAC_MAX_SIZE 64
/**
* @brief Generates the timeblock for a time in seconds.
* Timeblocks are the amount of intervals in a given time. For example,
* if 1,000,000 seconds has passed for 30 second intervals, you would get
* 33,333 timeblocks (intervals), where timeblock++ is effectively +30 seconds.
* @param interval in seconds
* @param for_time a time in seconds to get the current timeblocks
* @return Timeblock given \p for_time using \p interval
*/
uint64_t totp_timecode(uint8_t interval, uint64_t for_time) {
return for_time / interval;
}
/**
* @brief Generates an OTP (One Time Password)
* @param algo hashing algorithm to be used
* @param digits desired TOTP code length
* @param plain_secret plain token secret
* @param plain_secret_length plain token secret length
* @param input input data for OTP code generation
* @return OTP code if code was successfully generated; 0 otherwise
*/
uint32_t otp_generate(
TOTP_ALGO algo,
uint8_t digits,
const uint8_t* plain_secret,
size_t plain_secret_length,
uint64_t input) {
uint8_t hmac[HMAC_MAX_SIZE] = {0};
uint64_t input_swapped = swap_uint64(input);
int hmac_len =
(*algo)(plain_secret, plain_secret_length, (uint8_t*)&input_swapped, 8, &hmac[0]);
if(hmac_len == 0) {
return OTP_ERROR;
}
uint64_t offset = (hmac[hmac_len - 1] & 0xF);
uint64_t i_code =
((hmac[offset] & 0x7F) << 24 | (hmac[offset + 1] & 0xFF) << 16 |
(hmac[offset + 2] & 0xFF) << 8 | (hmac[offset + 3] & 0xFF));
i_code %= (uint64_t)pow(10, digits);
return i_code;
}
uint32_t totp_at(
TOTP_ALGO algo,
uint8_t digits,
const uint8_t* plain_secret,
size_t plain_secret_length,
uint64_t for_time,
float timezone,
uint8_t interval) {
uint64_t for_time_adjusted =
timezone_offset_apply(for_time, timezone_offset_from_hours(timezone));
return otp_generate(
algo,
digits,
plain_secret,
plain_secret_length,
totp_timecode(interval, for_time_adjusted));
}
static int totp_algo_sha1(
const uint8_t* key,
size_t key_length,
const uint8_t* input,
size_t input_length,
uint8_t* output) {
hmac_sha1(key, key_length, input, input_length, output);
return HMAC_SHA1_RESULT_SIZE;
}
static int totp_algo_sha256(
const uint8_t* key,
size_t key_length,
const uint8_t* input,
size_t input_length,
uint8_t* output) {
hmac_sha256(key, key_length, input, input_length, output);
return HMAC_SHA256_RESULT_SIZE;
}
static int totp_algo_sha512(
const uint8_t* key,
size_t key_length,
const uint8_t* input,
size_t input_length,
uint8_t* output) {
hmac_sha512(key, key_length, input, input_length, output);
return HMAC_SHA512_RESULT_SIZE;
}
const TOTP_ALGO TOTP_ALGO_SHA1 = (TOTP_ALGO)(&totp_algo_sha1);
const TOTP_ALGO TOTP_ALGO_SHA256 = (TOTP_ALGO)(&totp_algo_sha256);
const TOTP_ALGO TOTP_ALGO_SHA512 = (TOTP_ALGO)(&totp_algo_sha512);
@@ -0,0 +1,57 @@
#pragma once
#include <stdlib.h>
#include <stdint.h>
#define OTP_ERROR (0)
/**
* @brief Must compute HMAC using passed arguments, output as char array through output.
* \p key is secret key buffer.
* \p key_length is secret key buffer length.
* \p input is input buffer.
* \p input_length is input buffer length.
* \p output is an output buffer of the resulting HMAC operation.
* Must return 0 if error, or the length in bytes of the HMAC operation.
*/
typedef int (*TOTP_ALGO)(
const uint8_t* key,
size_t key_length,
const uint8_t* input,
size_t input_length,
uint8_t* output);
/**
* @brief Computes HMAC using SHA1
*/
extern const TOTP_ALGO TOTP_ALGO_SHA1;
/**
* @brief Computes HMAC using SHA256
*/
extern const TOTP_ALGO TOTP_ALGO_SHA256;
/**
* @brief Computes HMAC using SHA512
*/
extern const TOTP_ALGO TOTP_ALGO_SHA512;
/**
* @brief Generates a OTP key using the totp algorithm.
* @param algo hashing algorithm to be used
* @param digits desired TOTP code length
* @param plain_secret plain token secret
* @param plain_secret_length plain token secret length
* @param for_time the time the generated key will be created for
* @param timezone UTC timezone adjustment for the generated key
* @param interval token lifetime in seconds
* @return TOTP code if code was successfully generated; 0 otherwise
*/
uint32_t totp_at(
TOTP_ALGO algo,
uint8_t digits,
const uint8_t* plain_secret,
size_t plain_secret_length,
uint64_t for_time,
float timezone,
uint8_t interval);
Binary file not shown.

After

Width:  |  Height:  |  Size: 496 B

@@ -0,0 +1,197 @@
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <input/input.h>
#include <dialogs/dialogs.h>
#include <stdlib.h>
#include <flipper_format/flipper_format.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include "services/config/config.h"
#include "types/plugin_state.h"
#include "types/token_info.h"
#include "types/plugin_event.h"
#include "types/event_type.h"
#include "types/common.h"
#include "ui/scene_director.h"
#include "ui/constants.h"
#include "ui/common_dialogs.h"
#include "services/crypto/crypto.h"
#include "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) {
totp_scene_director_render(canvas, plugin_state);
}
release_mutex((ValueMutex*)ctx, plugin_state);
}
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
furi_assert(event_queue);
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static bool totp_activate_initial_scene(PluginState* const plugin_state) {
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);
DialogMessageButton dialog_result =
dialog_message_show(plugin_state->dialogs_app, message);
dialog_message_free(message);
if(dialog_result == DialogMessageButtonRight) {
totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL);
} else {
if(!totp_crypto_seed_iv(plugin_state, NULL, 0)) {
totp_dialogs_config_loading_error(plugin_state);
return false;
}
totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
}
} else if(plugin_state->pin_set) {
totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL);
} else {
if(!totp_crypto_seed_iv(plugin_state, NULL, 0)) {
totp_dialogs_config_loading_error(plugin_state);
return false;
}
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");
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_show(plugin_state->dialogs_app, message);
dialog_message_free(message);
return false;
}
}
return true;
}
static bool totp_plugin_state_init(PluginState* const plugin_state) {
plugin_state->gui = furi_record_open(RECORD_GUI);
plugin_state->notification_app = furi_record_open(RECORD_NOTIFICATION);
plugin_state->dialogs_app = furi_record_open(RECORD_DIALOGS);
if(totp_config_file_load_base(plugin_state) != TotpConfigFileOpenSuccess) {
totp_dialogs_config_loading_error(plugin_state);
return false;
}
return true;
}
static void totp_plugin_state_free(PluginState* plugin_state) {
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_DIALOGS);
ListNode* node = plugin_state->tokens_list;
ListNode* tmp;
while(node != NULL) {
tmp = node->next;
TokenInfo* tokenInfo = node->data;
token_info_free(tokenInfo);
free(node);
node = tmp;
}
if(plugin_state->crypto_verify_data != NULL) {
free(plugin_state->crypto_verify_data);
}
free(plugin_state);
}
int32_t totp_app() {
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
PluginState* plugin_state = malloc(sizeof(PluginState));
furi_check(plugin_state != NULL);
if(!totp_plugin_state_init(plugin_state)) {
FURI_LOG_E(LOGGING_TAG, "App state initialization failed\r\n");
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");
totp_plugin_state_free(plugin_state);
return 255;
}
totp_cli_register_command_handler(plugin_state);
totp_scene_director_init_scenes(plugin_state);
if(!totp_activate_initial_scene(plugin_state)) {
FURI_LOG_E(LOGGING_TAG, "An error ocurred during activating initial scene\r\n");
totp_plugin_state_free(plugin_state);
return 253;
}
// Set system callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
view_port_input_callback_set(view_port, input_callback, event_queue);
// Open GUI and register view_port
gui_add_view_port(plugin_state->gui, view_port, GuiLayerFullscreen);
PluginEvent event;
bool processing = true;
uint32_t last_user_interaction_time = furi_get_tick();
while(processing) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
PluginState* plugin_state_m = acquire_mutex_block(&state_mutex);
if(event_status == FuriStatusOk) {
if(event.type == EventTypeKey) {
last_user_interaction_time = furi_get_tick();
}
processing = totp_scene_director_handle_event(&event, plugin_state_m);
} else if(
plugin_state_m->pin_set && plugin_state_m->current_scene != TotpSceneAuthentication &&
furi_get_tick() - last_user_interaction_time > IDLE_TIMEOUT) {
totp_scene_director_activate_scene(plugin_state_m, TotpSceneAuthentication, NULL);
}
view_port_update(view_port);
release_mutex(&state_mutex, plugin_state_m);
}
totp_cli_unregister_command_handler();
totp_scene_director_deactivate_active_scene(plugin_state);
totp_scene_director_dispose(plugin_state);
view_port_enabled_set(view_port, false);
gui_remove_view_port(plugin_state->gui, view_port);
view_port_free(view_port);
furi_message_queue_free(event_queue);
delete_mutex(&state_mutex);
totp_plugin_state_free(plugin_state);
return 0;
}
@@ -0,0 +1,3 @@
#pragma once
#define LOGGING_TAG "TOTP APP"
@@ -0,0 +1,9 @@
#pragma once
#include <inttypes.h>
typedef uint8_t EventType;
enum EventTypes {
EventTypeTick,
EventTypeKey,
};
@@ -0,0 +1,9 @@
#pragma once
typedef uint8_t NotificationMethod;
enum NotificationMethods {
NotificationMethodNone = 0b00,
NotificationMethodSound = 0b01,
NotificationMethodVibro = 0b10,
};
@@ -0,0 +1,17 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#define TOTP_NULLABLE_STRUCT(value_type) \
typedef struct TotpNullable_##value_type { \
bool is_null; \
value_type value; \
} TotpNullable_##value_type
#define TOTP_NULLABLE_NULL(s) s.is_null = true
#define TOTP_NULLABLE_VALUE(s, v) \
s.is_null = false; \
s.value = v
TOTP_NULLABLE_STRUCT(uint16_t);
@@ -0,0 +1,10 @@
#pragma once
#include <inttypes.h>
#include <input/input.h>
#include "event_type.h"
typedef struct {
EventType type;
InputEvent input;
} PluginEvent;
@@ -0,0 +1,90 @@
#pragma once
#include <notification/notification.h>
#include <gui/gui.h>
#include <dialogs/dialogs.h>
#include "../lib/list/list.h"
#include "../ui/totp_scenes_enum.h"
#include "notification_method.h"
#define TOTP_IV_SIZE 16
/**
* @brief Application state structure
*/
typedef struct {
/**
* @brief Application current scene
*/
Scene current_scene;
/**
* @brief Application current scene state
*/
void* current_scene_state;
/**
* @brief Reference to the firmware notification subsystem
*/
NotificationApp* notification_app;
/**
* @brief Reference to the firmware dialogs subsystem
*/
DialogsApp* dialogs_app;
/**
* @brief Reference to the firmware GUI subsystem
*/
Gui* gui;
/**
* @brief Timezone UTC offset in hours
*/
float timezone_offset;
/**
* @brief Token list head node
*/
ListNode* tokens_list;
/**
* @brief Whether token list is loaded or not
*/
bool token_list_loaded;
/**
* @brief Tokens list length
*/
uint16_t tokens_count;
/**
* @brief Encrypted well-known string data
*/
uint8_t* crypto_verify_data;
/**
* @brief Encrypted well-known string data length
*/
size_t crypto_verify_data_length;
/**
* @brief Whether PIN is set by user or not
*/
bool pin_set;
/**
* @brief Initialization vector (IV) to be used for encryption\decryption
*/
uint8_t iv[TOTP_IV_SIZE];
/**
* @brief Basic randomly-generated initialization vector (IV)
*/
uint8_t base_iv[TOTP_IV_SIZE];
/**
* @brief Notification method
*/
NotificationMethod notification_method;
} PluginState;
@@ -0,0 +1,61 @@
#include <furi/furi.h>
#include <furi_hal.h>
#include "token_info.h"
#include "stdlib.h"
#include "common.h"
#include "../lib/base32/base32.h"
#include "../services/crypto/crypto.h"
#include "../lib/polyfills/memset_s.h"
TokenInfo* token_info_alloc() {
TokenInfo* tokenInfo = malloc(sizeof(TokenInfo));
furi_check(tokenInfo != NULL);
tokenInfo->algo = SHA1;
tokenInfo->digits = TOTP_6_DIGITS;
return tokenInfo;
}
void token_info_free(TokenInfo* token_info) {
if(token_info == NULL) return;
free(token_info->name);
free(token_info->token);
free(token_info);
}
bool token_info_set_secret(
TokenInfo* token_info,
const char* base32_token_secret,
size_t token_secret_length,
const uint8_t* iv) {
uint8_t* plain_secret = malloc(token_secret_length);
furi_check(plain_secret != NULL);
int plain_secret_length =
base32_decode((const uint8_t*)base32_token_secret, plain_secret, token_secret_length);
bool result;
if(plain_secret_length >= 0) {
token_info->token =
totp_crypto_encrypt(plain_secret, plain_secret_length, iv, &token_info->token_length);
result = true;
} else {
result = false;
}
memset_s(plain_secret, token_secret_length, 0, token_secret_length);
free(plain_secret);
return result;
}
bool token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits) {
switch(digits) {
case 6:
token_info->digits = TOTP_6_DIGITS;
return true;
case 8:
token_info->digits = TOTP_8_DIGITS;
return true;
default:
break;
}
return false;
}
@@ -0,0 +1,107 @@
#pragma once
#include <inttypes.h>
typedef uint8_t TokenHashAlgo;
typedef uint8_t TokenDigitsCount;
/**
* @brief Hashing algorithm to be used to generate token
*/
enum TokenHashAlgos {
/**
* @brief SHA1 hashing algorithm
*/
SHA1,
/**
* @brief SHA256 hashing algorithm
*/
SHA256,
/**
* @brief SHA512 hashing algorithm
*/
SHA512
};
/**
* @brief Token digits count to be generated.
*/
enum TokenDigitsCounts {
/**
* @brief 6 digits
*/
TOTP_6_DIGITS = 6,
/**
* @brief 8 digits
*/
TOTP_8_DIGITS = 8
};
#define TOTP_TOKEN_DIGITS_MAX_COUNT 8
/**
* @brief TOTP token information
*/
typedef struct {
/**
* @brief Encrypted token secret
*/
uint8_t* token;
/**
* @brief Encrypted token secret length
*/
size_t token_length;
/**
* @brief User-friendly token name
*/
char* name;
/**
* @brief Hashing algorithm
*/
TokenHashAlgo algo;
/**
* @brief Desired TOTP token length
*/
TokenDigitsCount digits;
} TokenInfo;
/**
* @brief Allocates a new instance of \c TokenInfo
* @return
*/
TokenInfo* token_info_alloc();
/**
* @brief Disposes all the resources allocated by the given \c TokenInfo instance
* @param token_info instance to be disposed
*/
void token_info_free(TokenInfo* token_info);
/**
* @brief Encrypts & sets plain token secret to the given instance of \c TokenInfo
* @param token_info instance where secret should be updated
* @param base32_token_secret plain token secret in Base32 format
* @param token_secret_length plain token secret length
* @param iv initialization vecor (IV) to be used for encryption
* @return \c true if token successfully set; \c false otherwise
*/
bool token_info_set_secret(
TokenInfo* token_info,
const char* base32_token_secret,
size_t token_secret_length,
const uint8_t* iv);
/**
* @brief Sets token digits count from \c uint8_t value
* @param token_info instance whichs token digits count length should be updated
* @param digits desired token digits count length
* @return \c true if token digits count length has been updated; \c false p
*/
bool token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits);
@@ -0,0 +1,10 @@
#pragma once
typedef uint8_t TotpUserPinCode;
enum TotpUserPinCodes {
PinCodeArrowUp = 2,
PinCodeArrowRight = 8,
PinCodeArrowDown = 11,
PinCodeArrowLeft = 5
};
@@ -0,0 +1,20 @@
#include "common_dialogs.h"
#include "constants.h"
static DialogMessageButton totp_dialogs_common(PluginState* plugin_state, const char* text) {
DialogMessage* message = dialog_message_alloc();
dialog_message_set_buttons(message, "Exit", NULL, NULL);
dialog_message_set_text(
message, text, SCREEN_WIDTH_CENTER, SCREEN_HEIGHT_CENTER, AlignCenter, AlignCenter);
DialogMessageButton result = dialog_message_show(plugin_state->dialogs_app, message);
dialog_message_free(message);
return result;
}
DialogMessageButton totp_dialogs_config_loading_error(PluginState* plugin_state) {
return totp_dialogs_common(plugin_state, "An error has occurred\nduring loading config file");
}
DialogMessageButton totp_dialogs_config_updating_error(PluginState* plugin_state) {
return totp_dialogs_common(plugin_state, "An error has occurred\nduring updating config file");
}
@@ -0,0 +1,7 @@
#pragma once
#include <dialogs/dialogs.h>
#include "../types/plugin_state.h"
DialogMessageButton totp_dialogs_config_loading_error(PluginState* plugin_state);
DialogMessageButton totp_dialogs_config_updating_error(PluginState* plugin_state);
@@ -0,0 +1,6 @@
#pragma once
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define SCREEN_WIDTH_CENTER (SCREEN_WIDTH >> 1)
#define SCREEN_HEIGHT_CENTER (SCREEN_HEIGHT >> 1)
@@ -0,0 +1,128 @@
#include "../types/common.h"
#include "scene_director.h"
#include "scenes/authenticate/totp_scene_authenticate.h"
#include "scenes/generate_token/totp_scene_generate_token.h"
#include "scenes/add_new_token/totp_scene_add_new_token.h"
#include "scenes/token_menu/totp_scene_token_menu.h"
#include "scenes/app_settings/totp_app_settings.h"
void totp_scene_director_activate_scene(
PluginState* const plugin_state,
Scene scene,
const void* context) {
totp_scene_director_deactivate_active_scene(plugin_state);
switch(scene) {
case TotpSceneGenerateToken:
totp_scene_generate_token_activate(plugin_state, context);
break;
case TotpSceneAuthentication:
totp_scene_authenticate_activate(plugin_state);
break;
case TotpSceneAddNewToken:
totp_scene_add_new_token_activate(plugin_state, context);
break;
case TotpSceneTokenMenu:
totp_scene_token_menu_activate(plugin_state, context);
break;
case TotpSceneAppSettings:
totp_scene_app_settings_activate(plugin_state, context);
break;
case TotpSceneNone:
break;
default:
break;
}
plugin_state->current_scene = scene;
}
void totp_scene_director_deactivate_active_scene(PluginState* const plugin_state) {
switch(plugin_state->current_scene) {
case TotpSceneGenerateToken:
totp_scene_generate_token_deactivate(plugin_state);
break;
case TotpSceneAuthentication:
totp_scene_authenticate_deactivate(plugin_state);
break;
case TotpSceneAddNewToken:
totp_scene_add_new_token_deactivate(plugin_state);
break;
case TotpSceneTokenMenu:
totp_scene_token_menu_deactivate(plugin_state);
break;
case TotpSceneAppSettings:
totp_scene_app_settings_deactivate(plugin_state);
break;
case TotpSceneNone:
break;
default:
break;
}
}
void totp_scene_director_init_scenes(PluginState* const plugin_state) {
totp_scene_authenticate_init(plugin_state);
totp_scene_generate_token_init(plugin_state);
totp_scene_add_new_token_init(plugin_state);
totp_scene_token_menu_init(plugin_state);
totp_scene_app_settings_init(plugin_state);
}
void totp_scene_director_render(Canvas* const canvas, PluginState* const plugin_state) {
switch(plugin_state->current_scene) {
case TotpSceneGenerateToken:
totp_scene_generate_token_render(canvas, plugin_state);
break;
case TotpSceneAuthentication:
totp_scene_authenticate_render(canvas, plugin_state);
break;
case TotpSceneAddNewToken:
totp_scene_add_new_token_render(canvas, plugin_state);
break;
case TotpSceneTokenMenu:
totp_scene_token_menu_render(canvas, plugin_state);
break;
case TotpSceneAppSettings:
totp_scene_app_settings_render(canvas, plugin_state);
break;
case TotpSceneNone:
break;
default:
break;
}
}
void totp_scene_director_dispose(const PluginState* const plugin_state) {
totp_scene_generate_token_free(plugin_state);
totp_scene_authenticate_free(plugin_state);
totp_scene_add_new_token_free(plugin_state);
totp_scene_token_menu_free(plugin_state);
totp_scene_app_settings_free(plugin_state);
}
bool totp_scene_director_handle_event(PluginEvent* const event, PluginState* const plugin_state) {
bool processing = true;
switch(plugin_state->current_scene) {
case TotpSceneGenerateToken:
processing = totp_scene_generate_token_handle_event(event, plugin_state);
break;
case TotpSceneAuthentication:
processing = totp_scene_authenticate_handle_event(event, plugin_state);
break;
case TotpSceneAddNewToken:
processing = totp_scene_add_new_token_handle_event(event, plugin_state);
break;
case TotpSceneTokenMenu:
processing = totp_scene_token_menu_handle_event(event, plugin_state);
break;
case TotpSceneAppSettings:
processing = totp_scene_app_settings_handle_event(event, plugin_state);
break;
case TotpSceneNone:
break;
default:
break;
}
return processing;
}
@@ -0,0 +1,50 @@
#pragma once
#include <gui/gui.h>
#include "../types/plugin_state.h"
#include "../types/plugin_event.h"
#include "totp_scenes_enum.h"
/**
* @brief Activates scene
* @param plugin_state application state
* @param scene scene to be activated
* @param context scene context to be passed to the scene activation method
*/
void totp_scene_director_activate_scene(
PluginState* const plugin_state,
Scene scene,
const void* context);
/**
* @brief Deactivate current scene
* @param plugin_state application state
*/
void totp_scene_director_deactivate_active_scene(PluginState* const plugin_state);
/**
* @brief Initializes all the available scenes
* @param plugin_state application state
*/
void totp_scene_director_init_scenes(PluginState* const plugin_state);
/**
* @brief Renders current scene
* @param canvas canvas to render at
* @param plugin_state application state
*/
void totp_scene_director_render(Canvas* const canvas, PluginState* const plugin_state);
/**
* @brief Disposes all the available scenes
* @param plugin_state application state
*/
void totp_scene_director_dispose(const PluginState* const plugin_state);
/**
* @brief Handles application event for the current scene
* @param event event to be handled
* @param plugin_state application state
* @return \c true if event handled and applilcation should continue; \c false if application should be closed
*/
bool totp_scene_director_handle_event(PluginEvent* const event, PluginState* const plugin_state);
@@ -0,0 +1,82 @@
#include "totp_input_text.h"
#include <gui/view_i.h>
#include "../../../lib/polyfills/strnlen.h"
void view_draw(View* view, Canvas* canvas) {
furi_assert(view);
if(view->draw_callback) {
void* data = view_get_model(view);
view->draw_callback(canvas, data);
view_unlock_model(view);
}
}
bool view_input(View* view, InputEvent* event) {
furi_assert(view);
if(view->input_callback) {
return view->input_callback(event, view->context);
} else {
return false;
}
}
void view_unlock_model(View* view) {
furi_assert(view);
if(view->model_type == ViewModelTypeLocking) {
ViewModelLocking* model = (ViewModelLocking*)(view->model);
furi_check(furi_mutex_release(model->mutex) == FuriStatusOk);
}
}
static void commit_text_input_callback(void* context) {
InputTextSceneState* text_input_state = (InputTextSceneState*)context;
if(text_input_state->callback != 0) {
InputTextSceneCallbackResult* result = malloc(sizeof(InputTextSceneCallbackResult));
furi_check(result != NULL);
result->user_input_length =
strnlen(text_input_state->text_input_buffer, INPUT_BUFFER_SIZE);
result->user_input = malloc(result->user_input_length + 1);
furi_check(result->user_input != NULL);
result->callback_data = text_input_state->callback_data;
strlcpy(
result->user_input,
text_input_state->text_input_buffer,
result->user_input_length + 1);
text_input_state->callback(result);
}
}
InputTextSceneState* totp_input_text_activate(InputTextSceneContext* context) {
InputTextSceneState* text_input_state = malloc(sizeof(InputTextSceneState));
furi_check(text_input_state != NULL);
text_input_state->text_input = text_input_alloc();
text_input_state->text_input_view = text_input_get_view(text_input_state->text_input);
text_input_state->callback = context->callback;
text_input_state->callback_data = context->callback_data;
text_input_set_header_text(text_input_state->text_input, context->header_text);
text_input_set_result_callback(
text_input_state->text_input,
commit_text_input_callback,
text_input_state,
&text_input_state->text_input_buffer[0],
INPUT_BUFFER_SIZE,
true);
return text_input_state;
}
void totp_input_text_render(Canvas* const canvas, InputTextSceneState* text_input_state) {
view_draw(text_input_state->text_input_view, canvas);
}
bool totp_input_text_handle_event(PluginEvent* const event, InputTextSceneState* text_input_state) {
if(event->type == EventTypeKey) {
view_input(text_input_state->text_input_view, &event->input);
}
return true;
}
void totp_input_text_free(InputTextSceneState* state) {
text_input_free(state->text_input);
free(state);
}
@@ -0,0 +1,36 @@
#pragma once
#include <gui/gui.h>
#include <gui/view.h>
#include <gui/modules/text_input.h>
#include "../../../types/plugin_state.h"
#include "../../../types/plugin_event.h"
typedef struct {
char* user_input;
size_t user_input_length;
void* callback_data;
} InputTextSceneCallbackResult;
typedef void (*InputTextSceneCallback)(InputTextSceneCallbackResult* result);
typedef struct {
InputTextSceneCallback callback;
char* header_text;
void* callback_data;
} InputTextSceneContext;
#define INPUT_BUFFER_SIZE 255
typedef struct {
TextInput* text_input;
View* text_input_view;
char text_input_buffer[INPUT_BUFFER_SIZE];
InputTextSceneCallback callback;
void* callback_data;
} InputTextSceneState;
InputTextSceneState* totp_input_text_activate(InputTextSceneContext* context);
void totp_input_text_render(Canvas* const canvas, InputTextSceneState* text_input_state);
bool totp_input_text_handle_event(PluginEvent* const event, InputTextSceneState* text_input_state);
void totp_input_text_free(InputTextSceneState* state);
@@ -0,0 +1,323 @@
#include "totp_scene_add_new_token.h"
#include "../../../types/common.h"
#include "../../constants.h"
#include "../../scene_director.h"
#include "totp_input_text.h"
#include "../../../types/token_info.h"
#include "../../../lib/list/list.h"
#include "../../../services/config/config.h"
#include "../../ui_controls.h"
#include "../../common_dialogs.h"
#include "../../../lib/roll_value/roll_value.h"
#include "../../../types/nullable.h"
#include "../generate_token/totp_scene_generate_token.h"
char* TOKEN_ALGO_LIST[] = {"SHA1", "SHA256", "SHA512"};
char* TOKEN_DIGITS_TEXT_LIST[] = {"6 digits", "8 digits"};
TokenDigitsCount TOKEN_DIGITS_VALUE_LIST[] = {TOTP_6_DIGITS, TOTP_8_DIGITS};
typedef enum {
TokenNameTextBox,
TokenSecretTextBox,
TokenAlgoSelect,
TokenLengthSelect,
ConfirmButton,
} Control;
typedef struct {
char* token_name;
size_t token_name_length;
char* token_secret;
size_t token_secret_length;
bool saved;
Control selected_control;
InputTextSceneContext* token_name_input_context;
InputTextSceneContext* token_secret_input_context;
InputTextSceneState* input_state;
uint32_t input_started_at;
TotpNullable_uint16_t current_token_index;
int16_t screen_y_offset;
TokenHashAlgo algo;
uint8_t digits_count_index;
} SceneState;
void totp_scene_add_new_token_init(const PluginState* plugin_state) {
UNUSED(plugin_state);
}
static void on_token_name_user_comitted(InputTextSceneCallbackResult* result) {
SceneState* scene_state = result->callback_data;
free(scene_state->token_name);
scene_state->token_name = result->user_input;
scene_state->token_name_length = result->user_input_length;
scene_state->input_started_at = 0;
free(result);
}
static void on_token_secret_user_comitted(InputTextSceneCallbackResult* result) {
SceneState* scene_state = result->callback_data;
free(scene_state->token_secret);
scene_state->token_secret = result->user_input;
scene_state->token_secret_length = result->user_input_length;
scene_state->input_started_at = 0;
free(result);
}
void totp_scene_add_new_token_activate(
PluginState* plugin_state,
const TokenAddEditSceneContext* context) {
SceneState* scene_state = malloc(sizeof(SceneState));
furi_check(scene_state != NULL);
plugin_state->current_scene_state = scene_state;
scene_state->token_name = "Name";
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->token_name_input_context = malloc(sizeof(InputTextSceneContext));
furi_check(scene_state->token_name_input_context != NULL);
scene_state->token_name_input_context->header_text = "Enter token name";
scene_state->token_name_input_context->callback_data = scene_state;
scene_state->token_name_input_context->callback = on_token_name_user_comitted;
scene_state->token_secret_input_context = malloc(sizeof(InputTextSceneContext));
furi_check(scene_state->token_secret_input_context != NULL);
scene_state->token_secret_input_context->header_text = "Enter token secret";
scene_state->token_secret_input_context->callback_data = scene_state;
scene_state->token_secret_input_context->callback = on_token_secret_user_comitted;
scene_state->screen_y_offset = 0;
scene_state->input_state = NULL;
if(context == NULL) {
TOTP_NULLABLE_NULL(scene_state->current_token_index);
} else {
TOTP_NULLABLE_VALUE(scene_state->current_token_index, context->current_token_index);
}
}
void totp_scene_add_new_token_render(Canvas* const canvas, PluginState* plugin_state) {
SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
if(scene_state->input_started_at > 0) {
totp_input_text_render(canvas, scene_state->input_state);
return;
}
ui_control_text_box_render(
canvas,
10 - scene_state->screen_y_offset,
scene_state->token_name,
scene_state->selected_control == TokenNameTextBox);
ui_control_text_box_render(
canvas,
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);
ui_control_select_render(
canvas,
0,
63 - scene_state->screen_y_offset,
SCREEN_WIDTH,
TOKEN_DIGITS_TEXT_LIST[scene_state->digits_count_index],
scene_state->selected_control == TokenLengthSelect);
ui_control_button_render(
canvas,
SCREEN_WIDTH_CENTER - 24,
85 - scene_state->screen_y_offset,
48,
13,
"Confirm",
scene_state->selected_control == ConfirmButton);
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 0, 0, SCREEN_WIDTH, 10);
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, "Add new token");
canvas_set_font(canvas, FontSecondary);
}
void update_screen_y_offset(SceneState* scene_state) {
if(scene_state->selected_control > TokenAlgoSelect) {
scene_state->screen_y_offset = 35;
} else {
scene_state->screen_y_offset = 0;
}
}
bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState* plugin_state) {
if(event->type != EventTypeKey) {
return true;
}
SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
if(scene_state->input_started_at > 0 &&
furi_get_tick() - scene_state->input_started_at > 300) {
return totp_input_text_handle_event(event, scene_state->input_state);
}
if(event->input.type == InputTypeLong && event->input.key == InputKeyBack) {
return false;
}
if(event->input.type != InputTypePress) {
return true;
}
switch(event->input.key) {
case InputKeyUp:
totp_roll_value_uint8_t(
&scene_state->selected_control,
-1,
TokenNameTextBox,
ConfirmButton,
RollOverflowBehaviorStop);
update_screen_y_offset(scene_state);
break;
case InputKeyDown:
totp_roll_value_uint8_t(
&scene_state->selected_control,
1,
TokenNameTextBox,
ConfirmButton,
RollOverflowBehaviorStop);
update_screen_y_offset(scene_state);
break;
case InputKeyRight:
if(scene_state->selected_control == TokenAlgoSelect) {
totp_roll_value_uint8_t(&scene_state->algo, 1, SHA1, SHA512, RollOverflowBehaviorRoll);
} else if(scene_state->selected_control == TokenLengthSelect) {
totp_roll_value_uint8_t(
&scene_state->digits_count_index, 1, 0, 1, RollOverflowBehaviorRoll);
}
break;
case InputKeyLeft:
if(scene_state->selected_control == TokenAlgoSelect) {
totp_roll_value_uint8_t(
&scene_state->algo, -1, SHA1, SHA512, RollOverflowBehaviorRoll);
} else if(scene_state->selected_control == TokenLengthSelect) {
totp_roll_value_uint8_t(
&scene_state->digits_count_index, -1, 0, 1, RollOverflowBehaviorRoll);
}
break;
case InputKeyOk:
switch(scene_state->selected_control) {
case TokenNameTextBox:
if(scene_state->input_state != NULL) {
totp_input_text_free(scene_state->input_state);
}
scene_state->input_state =
totp_input_text_activate(scene_state->token_name_input_context);
scene_state->input_started_at = furi_get_tick();
break;
case TokenSecretTextBox:
if(scene_state->input_state != NULL) {
totp_input_text_free(scene_state->input_state);
}
scene_state->input_state =
totp_input_text_activate(scene_state->token_secret_input_context);
scene_state->input_started_at = furi_get_tick();
break;
case TokenAlgoSelect:
break;
case TokenLengthSelect:
break;
case ConfirmButton: {
TokenInfo* tokenInfo = token_info_alloc();
bool token_secret_set = token_info_set_secret(
tokenInfo,
scene_state->token_secret,
scene_state->token_secret_length,
&plugin_state->iv[0]);
if(token_secret_set) {
tokenInfo->name = malloc(scene_state->token_name_length + 1);
furi_check(tokenInfo->name != NULL);
strlcpy(
tokenInfo->name, scene_state->token_name, scene_state->token_name_length + 1);
tokenInfo->algo = scene_state->algo;
tokenInfo->digits = TOKEN_DIGITS_VALUE_LIST[scene_state->digits_count_index];
TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, tokenInfo, furi_check);
plugin_state->tokens_count++;
if(totp_config_file_save_new_token(tokenInfo) != TotpConfigFileUpdateSuccess) {
token_info_free(tokenInfo);
totp_dialogs_config_updating_error(plugin_state);
return false;
}
GenerateTokenSceneContext generate_scene_context = {
.current_token_index = plugin_state->tokens_count - 1};
totp_scene_director_activate_scene(
plugin_state, TotpSceneGenerateToken, &generate_scene_context);
} else {
token_info_free(tokenInfo);
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);
}
break;
}
default:
break;
}
break;
case InputKeyBack:
if(!scene_state->current_token_index.is_null) {
GenerateTokenSceneContext generate_scene_context = {
.current_token_index = scene_state->current_token_index.value};
totp_scene_director_activate_scene(
plugin_state, TotpSceneGenerateToken, &generate_scene_context);
} else {
totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
}
break;
default:
break;
}
return true;
}
void totp_scene_add_new_token_deactivate(PluginState* plugin_state) {
if(plugin_state->current_scene_state == NULL) return;
SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
free(scene_state->token_name);
free(scene_state->token_secret);
free(scene_state->token_name_input_context->header_text);
free(scene_state->token_name_input_context);
free(scene_state->token_secret_input_context->header_text);
free(scene_state->token_secret_input_context);
if(scene_state->input_state != NULL) {
totp_input_text_free(scene_state->input_state);
}
free(plugin_state->current_scene_state);
plugin_state->current_scene_state = NULL;
}
void totp_scene_add_new_token_free(const PluginState* plugin_state) {
UNUSED(plugin_state);
}
@@ -0,0 +1,20 @@
#pragma once
#include <gui/gui.h>
#include <furi.h>
#include <furi_hal.h>
#include "../../../types/plugin_state.h"
#include "../../../types/plugin_event.h"
typedef struct {
uint16_t current_token_index;
} TokenAddEditSceneContext;
void totp_scene_add_new_token_init(const PluginState* plugin_state);
void totp_scene_add_new_token_activate(
PluginState* plugin_state,
const TokenAddEditSceneContext* context);
void totp_scene_add_new_token_render(Canvas* const canvas, PluginState* plugin_state);
bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState* plugin_state);
void totp_scene_add_new_token_deactivate(PluginState* plugin_state);
void totp_scene_add_new_token_free(const PluginState* plugin_state);
@@ -0,0 +1,250 @@
#include "totp_app_settings.h"
#include <math.h>
#include <totp_icons.h>
#include "../../ui_controls.h"
#include "../../common_dialogs.h"
#include "../../scene_director.h"
#include "../token_menu/totp_scene_token_menu.h"
#include "../../constants.h"
#include "../../../services/config/config.h"
#include "../../../services/convert/convert.h"
#include "../../../lib/roll_value/roll_value.h"
#include "../../../types/nullable.h"
char* YES_NO_LIST[] = {"NO", "YES"};
typedef enum { HoursInput, MinutesInput, Sound, Vibro, ConfirmButton } Control;
typedef struct {
int8_t tz_offset_hours;
uint8_t tz_offset_minutes;
bool notification_sound;
bool notification_vibro;
uint8_t y_offset;
TotpNullable_uint16_t current_token_index;
Control selected_control;
} SceneState;
void totp_scene_app_settings_init(const PluginState* plugin_state) {
UNUSED(plugin_state);
}
void totp_scene_app_settings_activate(
PluginState* plugin_state,
const AppSettingsSceneContext* context) {
SceneState* scene_state = malloc(sizeof(SceneState));
furi_check(scene_state != NULL);
plugin_state->current_scene_state = scene_state;
if(context != NULL) {
TOTP_NULLABLE_VALUE(scene_state->current_token_index, context->current_token_index);
} else {
TOTP_NULLABLE_NULL(scene_state->current_token_index);
}
float off_int;
float off_dec = modff(plugin_state->timezone_offset, &off_int);
scene_state->tz_offset_hours = off_int;
scene_state->tz_offset_minutes = 60.0f * off_dec;
scene_state->notification_sound = plugin_state->notification_method & NotificationMethodSound;
scene_state->notification_vibro = plugin_state->notification_method & NotificationMethodVibro;
}
static void two_digit_to_str(int8_t num, char* str) {
uint8_t index = 0;
if(num < 0) {
str[index++] = '-';
num = -num;
}
uint8_t d1 = (num / 10) % 10;
uint8_t d2 = num % 10;
str[index++] = CONVERT_DIGIT_TO_CHAR(d1);
str[index++] = CONVERT_DIGIT_TO_CHAR(d2);
str[index++] = '\0';
}
void totp_scene_app_settings_render(Canvas* const canvas, const PluginState* plugin_state) {
const SceneState* scene_state = plugin_state->current_scene_state;
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(
canvas, 0, 0 - scene_state->y_offset, AlignLeft, AlignTop, "Timezone offset");
canvas_set_font(canvas, FontSecondary);
char tmp_str[4];
two_digit_to_str(scene_state->tz_offset_hours, &tmp_str[0]);
canvas_draw_str_aligned(canvas, 0, 16 - scene_state->y_offset, AlignLeft, AlignTop, "Hours:");
ui_control_select_render(
canvas,
36,
10 - scene_state->y_offset,
SCREEN_WIDTH - 36,
&tmp_str[0],
scene_state->selected_control == HoursInput);
two_digit_to_str(scene_state->tz_offset_minutes, &tmp_str[0]);
canvas_draw_str_aligned(
canvas, 0, 34 - scene_state->y_offset, AlignLeft, AlignTop, "Minutes:");
ui_control_select_render(
canvas,
36,
28 - scene_state->y_offset,
SCREEN_WIDTH - 36,
&tmp_str[0],
scene_state->selected_control == MinutesInput);
canvas_draw_icon(
canvas,
SCREEN_WIDTH_CENTER - 5,
SCREEN_HEIGHT - 5 - scene_state->y_offset,
&I_totp_arrow_bottom_10x5);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(
canvas, 0, 64 - scene_state->y_offset, AlignLeft, AlignTop, "Notifications");
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(canvas, 0, 80 - scene_state->y_offset, AlignLeft, AlignTop, "Sound:");
ui_control_select_render(
canvas,
36,
74 - scene_state->y_offset,
SCREEN_WIDTH - 36,
YES_NO_LIST[scene_state->notification_sound],
scene_state->selected_control == Sound);
canvas_draw_str_aligned(canvas, 0, 98 - scene_state->y_offset, AlignLeft, AlignTop, "Vibro:");
ui_control_select_render(
canvas,
36,
92 - scene_state->y_offset,
SCREEN_WIDTH - 36,
YES_NO_LIST[scene_state->notification_vibro],
scene_state->selected_control == Vibro);
ui_control_button_render(
canvas,
SCREEN_WIDTH_CENTER - 24,
115 - scene_state->y_offset,
48,
13,
"Confirm",
scene_state->selected_control == ConfirmButton);
}
bool totp_scene_app_settings_handle_event(
const PluginEvent* const event,
PluginState* plugin_state) {
if(event->type != EventTypeKey) {
return true;
}
SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
if(event->input.type != InputTypePress && event->input.type != InputTypeRepeat) {
return true;
}
switch(event->input.key) {
case InputKeyUp:
totp_roll_value_uint8_t(
&scene_state->selected_control,
-1,
HoursInput,
ConfirmButton,
RollOverflowBehaviorStop);
if(scene_state->selected_control > MinutesInput) {
scene_state->y_offset = 64;
} else {
scene_state->y_offset = 0;
}
break;
case InputKeyDown:
totp_roll_value_uint8_t(
&scene_state->selected_control, 1, HoursInput, ConfirmButton, RollOverflowBehaviorStop);
if(scene_state->selected_control > MinutesInput) {
scene_state->y_offset = 64;
} else {
scene_state->y_offset = 0;
}
break;
case InputKeyRight:
if(scene_state->selected_control == HoursInput) {
totp_roll_value_int8_t(
&scene_state->tz_offset_hours, 1, -12, 12, RollOverflowBehaviorStop);
} else if(scene_state->selected_control == MinutesInput) {
totp_roll_value_uint8_t(
&scene_state->tz_offset_minutes, 15, 0, 45, RollOverflowBehaviorRoll);
} else if(scene_state->selected_control == Sound) {
scene_state->notification_sound = !scene_state->notification_sound;
} else if(scene_state->selected_control == Vibro) {
scene_state->notification_vibro = !scene_state->notification_vibro;
}
break;
case InputKeyLeft:
if(scene_state->selected_control == HoursInput) {
totp_roll_value_int8_t(
&scene_state->tz_offset_hours, -1, -12, 12, RollOverflowBehaviorStop);
} else if(scene_state->selected_control == MinutesInput) {
totp_roll_value_uint8_t(
&scene_state->tz_offset_minutes, -15, 0, 45, RollOverflowBehaviorRoll);
} else if(scene_state->selected_control == Sound) {
scene_state->notification_sound = !scene_state->notification_sound;
} else if(scene_state->selected_control == Vibro) {
scene_state->notification_vibro = !scene_state->notification_vibro;
}
break;
case InputKeyOk:
if(scene_state->selected_control == ConfirmButton) {
plugin_state->timezone_offset = (float)scene_state->tz_offset_hours +
(float)scene_state->tz_offset_minutes / 60.0f;
plugin_state->notification_method =
(scene_state->notification_sound ? NotificationMethodSound :
NotificationMethodNone) |
(scene_state->notification_vibro ? NotificationMethodVibro :
NotificationMethodNone);
if(totp_config_file_update_user_settings(plugin_state) !=
TotpConfigFileUpdateSuccess) {
totp_dialogs_config_updating_error(plugin_state);
return false;
}
if(!scene_state->current_token_index.is_null) {
TokenMenuSceneContext generate_scene_context = {
.current_token_index = scene_state->current_token_index.value};
totp_scene_director_activate_scene(
plugin_state, TotpSceneTokenMenu, &generate_scene_context);
} else {
totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL);
}
}
break;
case InputKeyBack: {
if(!scene_state->current_token_index.is_null) {
TokenMenuSceneContext generate_scene_context = {
.current_token_index = scene_state->current_token_index.value};
totp_scene_director_activate_scene(
plugin_state, TotpSceneTokenMenu, &generate_scene_context);
} else {
totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL);
}
break;
}
default:
break;
}
return true;
}
void totp_scene_app_settings_deactivate(PluginState* plugin_state) {
if(plugin_state->current_scene_state == NULL) return;
free(plugin_state->current_scene_state);
plugin_state->current_scene_state = NULL;
}
void totp_scene_app_settings_free(const PluginState* plugin_state) {
UNUSED(plugin_state);
}
@@ -0,0 +1,20 @@
#pragma once
#include <gui/gui.h>
#include "../../../types/plugin_state.h"
#include "../../../types/plugin_event.h"
typedef struct {
uint16_t current_token_index;
} AppSettingsSceneContext;
void totp_scene_app_settings_init(const PluginState* plugin_state);
void totp_scene_app_settings_activate(
PluginState* plugin_state,
const AppSettingsSceneContext* context);
void totp_scene_app_settings_render(Canvas* const canvas, const PluginState* plugin_state);
bool totp_scene_app_settings_handle_event(
const PluginEvent* const event,
PluginState* plugin_state);
void totp_scene_app_settings_deactivate(PluginState* plugin_state);
void totp_scene_app_settings_free(const PluginState* plugin_state);
@@ -0,0 +1,167 @@
#include "totp_scene_authenticate.h"
#include <dialogs/dialogs.h>
#include <totp_icons.h>
#include "../../../types/common.h"
#include "../../constants.h"
#include "../../../services/config/config.h"
#include "../../scene_director.h"
#include "../../totp_scenes_enum.h"
#include "../../../services/crypto/crypto.h"
#include "../../../types/user_pin_codes.h"
#define MAX_CODE_LENGTH TOTP_IV_SIZE
typedef struct {
TotpUserPinCode code_input[MAX_CODE_LENGTH];
uint8_t code_length;
} SceneState;
void totp_scene_authenticate_init(PluginState* plugin_state) {
memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE);
}
void totp_scene_authenticate_activate(PluginState* plugin_state) {
SceneState* scene_state = malloc(sizeof(SceneState));
furi_check(scene_state != NULL);
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) {
const SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
int v_shift = 0;
if(scene_state->code_length > 0) {
v_shift = -10;
}
if(plugin_state->crypto_verify_data == NULL) {
canvas_draw_str_aligned(
canvas,
SCREEN_WIDTH_CENTER,
SCREEN_HEIGHT_CENTER - 10 + v_shift,
AlignCenter,
AlignCenter,
"Use arrow keys");
canvas_draw_str_aligned(
canvas,
SCREEN_WIDTH_CENTER,
SCREEN_HEIGHT_CENTER + 5 + v_shift,
AlignCenter,
AlignCenter,
"to setup new PIN");
} else {
canvas_draw_str_aligned(
canvas,
SCREEN_WIDTH_CENTER,
SCREEN_HEIGHT_CENTER + v_shift,
AlignCenter,
AlignCenter,
"Use arrow keys to enter PIN");
}
const uint8_t PIN_ASTERISK_RADIUS = 3;
const uint8_t PIN_ASTERISK_STEP = (PIN_ASTERISK_RADIUS << 1) + 2;
if(scene_state->code_length > 0) {
uint8_t left_start_x = ((scene_state->code_length - 1) * PIN_ASTERISK_STEP) >> 1;
for(uint8_t i = 0; i < scene_state->code_length; i++) {
canvas_draw_disc(
canvas,
SCREEN_WIDTH_CENTER - left_start_x + i * PIN_ASTERISK_STEP,
SCREEN_HEIGHT_CENTER + 10,
PIN_ASTERISK_RADIUS);
}
}
}
bool totp_scene_authenticate_handle_event(
const PluginEvent* const event,
PluginState* plugin_state) {
if(event->type != EventTypeKey) {
return true;
}
if(event->input.type == InputTypeLong && event->input.key == InputKeyBack) {
return false;
}
if(event->input.type != InputTypePress) {
return true;
}
SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
switch(event->input.key) {
case InputKeyUp:
if(scene_state->code_length < MAX_CODE_LENGTH) {
scene_state->code_input[scene_state->code_length] = PinCodeArrowUp;
scene_state->code_length++;
}
break;
case InputKeyDown:
if(scene_state->code_length < MAX_CODE_LENGTH) {
scene_state->code_input[scene_state->code_length] = PinCodeArrowDown;
scene_state->code_length++;
}
break;
case InputKeyRight:
if(scene_state->code_length < MAX_CODE_LENGTH) {
scene_state->code_input[scene_state->code_length] = PinCodeArrowRight;
scene_state->code_length++;
}
break;
case InputKeyLeft:
if(scene_state->code_length < MAX_CODE_LENGTH) {
scene_state->code_input[scene_state->code_length] = PinCodeArrowLeft;
scene_state->code_length++;
}
break;
case InputKeyOk:
totp_crypto_seed_iv(plugin_state, &scene_state->code_input[0], scene_state->code_length);
if(totp_crypto_verify_key(plugin_state)) {
FURI_LOG_D(LOGGING_TAG, "PIN is valid");
totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
} else {
FURI_LOG_D(LOGGING_TAG, "PIN is NOT valid");
memset(&scene_state->code_input[0], 0, MAX_CODE_LENGTH);
memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE);
scene_state->code_length = 0;
DialogMessage* message = dialog_message_alloc();
dialog_message_set_buttons(message, "Try again", NULL, NULL);
dialog_message_set_header(
message,
"You entered\ninvalid PIN",
SCREEN_WIDTH_CENTER - 25,
SCREEN_HEIGHT_CENTER - 5,
AlignCenter,
AlignCenter);
dialog_message_set_icon(message, &I_DolphinCommon_56x48, 72, 17);
dialog_message_show(plugin_state->dialogs_app, message);
dialog_message_free(message);
}
break;
case InputKeyBack:
if(scene_state->code_length > 0) {
scene_state->code_input[scene_state->code_length - 1] = 0;
scene_state->code_length--;
}
break;
default:
break;
}
return true;
}
void totp_scene_authenticate_deactivate(PluginState* plugin_state) {
if(plugin_state->current_scene_state == NULL) return;
free(plugin_state->current_scene_state);
plugin_state->current_scene_state = NULL;
}
void totp_scene_authenticate_free(const PluginState* plugin_state) {
UNUSED(plugin_state);
}
@@ -0,0 +1,14 @@
#pragma once
#include <gui/gui.h>
#include "../../../types/plugin_state.h"
#include "../../../types/plugin_event.h"
void totp_scene_authenticate_init(PluginState* plugin_state);
void totp_scene_authenticate_activate(PluginState* plugin_state);
void totp_scene_authenticate_render(Canvas* const canvas, PluginState* plugin_state);
bool totp_scene_authenticate_handle_event(
const PluginEvent* const event,
PluginState* plugin_state);
void totp_scene_authenticate_deactivate(PluginState* plugin_state);
void totp_scene_authenticate_free(const PluginState* plugin_state);
@@ -0,0 +1,416 @@
#include <gui/gui.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <totp_icons.h>
#include "totp_scene_generate_token.h"
#include "../../../types/token_info.h"
#include "../../../types/common.h"
#include "../../constants.h"
#include "../../../services/totp/totp.h"
#include "../../../services/config/config.h"
#include "../../../services/crypto/crypto.h"
#include "../../../services/convert/convert.h"
#include "../../../lib/polyfills/memset_s.h"
#include "../../../lib/roll_value/roll_value.h"
#include "../../scene_director.h"
#include "../token_menu/totp_scene_token_menu.h"
#include "../../../workers/type_code/type_code.h"
#define TOKEN_LIFETIME 30
typedef struct {
uint16_t current_token_index;
char last_code[TOTP_TOKEN_DIGITS_MAX_COUNT + 1];
char* last_code_name;
bool need_token_update;
uint32_t last_token_gen_time;
TotpTypeCodeWorkerContext* type_code_worker_context;
NotificationMessage const** notification_sequence_new_token;
NotificationMessage const** notification_sequence_badusb;
} SceneState;
static const NotificationSequence*
get_notification_sequence_new_token(const PluginState* plugin_state, SceneState* scene_state) {
if(scene_state->notification_sequence_new_token == NULL) {
uint8_t i = 0;
uint8_t length = 4;
if(plugin_state->notification_method & NotificationMethodVibro) {
length += 2;
}
if(plugin_state->notification_method & NotificationMethodSound) {
length += 2;
}
scene_state->notification_sequence_new_token = malloc(sizeof(void*) * length);
furi_check(scene_state->notification_sequence_new_token != NULL);
scene_state->notification_sequence_new_token[i++] = &message_display_backlight_on;
scene_state->notification_sequence_new_token[i++] = &message_green_255;
if(plugin_state->notification_method & NotificationMethodVibro) {
scene_state->notification_sequence_new_token[i++] = &message_vibro_on;
}
if(plugin_state->notification_method & NotificationMethodSound) {
scene_state->notification_sequence_new_token[i++] = &message_note_c5;
}
scene_state->notification_sequence_new_token[i++] = &message_delay_50;
if(plugin_state->notification_method & NotificationMethodVibro) {
scene_state->notification_sequence_new_token[i++] = &message_vibro_off;
}
if(plugin_state->notification_method & NotificationMethodSound) {
scene_state->notification_sequence_new_token[i++] = &message_sound_off;
}
scene_state->notification_sequence_new_token[i++] = NULL;
}
return (NotificationSequence*)scene_state->notification_sequence_new_token;
}
static const NotificationSequence*
get_notification_sequence_badusb(const PluginState* plugin_state, SceneState* scene_state) {
if(scene_state->notification_sequence_badusb == NULL) {
uint8_t i = 0;
uint8_t length = 3;
if(plugin_state->notification_method & NotificationMethodVibro) {
length += 2;
}
if(plugin_state->notification_method & NotificationMethodSound) {
length += 6;
}
scene_state->notification_sequence_badusb = malloc(sizeof(void*) * length);
furi_check(scene_state->notification_sequence_badusb != NULL);
scene_state->notification_sequence_badusb[i++] = &message_blue_255;
if(plugin_state->notification_method & NotificationMethodVibro) {
scene_state->notification_sequence_badusb[i++] = &message_vibro_on;
}
if(plugin_state->notification_method & NotificationMethodSound) {
scene_state->notification_sequence_badusb[i++] = &message_note_d5; //-V525
scene_state->notification_sequence_badusb[i++] = &message_delay_50;
scene_state->notification_sequence_badusb[i++] = &message_note_e4;
scene_state->notification_sequence_badusb[i++] = &message_delay_50;
scene_state->notification_sequence_badusb[i++] = &message_note_f3;
}
scene_state->notification_sequence_badusb[i++] = &message_delay_50;
if(plugin_state->notification_method & NotificationMethodVibro) {
scene_state->notification_sequence_badusb[i++] = &message_vibro_off;
}
if(plugin_state->notification_method & NotificationMethodSound) {
scene_state->notification_sequence_badusb[i++] = &message_sound_off;
}
scene_state->notification_sequence_badusb[i++] = NULL;
}
return (NotificationSequence*)scene_state->notification_sequence_badusb;
}
static void int_token_to_str(uint32_t i_token_code, char* str, TokenDigitsCount len) {
if(i_token_code == OTP_ERROR) {
memset(&str[0], '-', len);
} else {
for(int i = len - 1; i >= 0; i--) {
str[i] = CONVERT_DIGIT_TO_CHAR(i_token_code % 10);
i_token_code = i_token_code / 10;
}
}
str[len] = '\0';
}
static TOTP_ALGO get_totp_algo_impl(TokenHashAlgo algo) {
switch(algo) {
case SHA1:
return TOTP_ALGO_SHA1;
case SHA256:
return TOTP_ALGO_SHA256;
case SHA512:
return TOTP_ALGO_SHA512;
default:
break;
}
return NULL;
}
static void update_totp_params(PluginState* const plugin_state) {
SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
if(scene_state->current_token_index < plugin_state->tokens_count) {
TokenInfo* tokenInfo =
list_element_at(plugin_state->tokens_list, scene_state->current_token_index)->data;
scene_state->need_token_update = true;
scene_state->last_code_name = tokenInfo->name;
}
}
void totp_scene_generate_token_init(const PluginState* plugin_state) {
UNUSED(plugin_state);
}
void totp_scene_generate_token_activate(
PluginState* plugin_state,
const GenerateTokenSceneContext* context) {
if(!plugin_state->token_list_loaded) {
TokenLoadingResult token_load_result = totp_config_file_load_tokens(plugin_state);
if(token_load_result != TokenLoadingResultSuccess) {
DialogMessage* message = dialog_message_alloc();
dialog_message_set_buttons(message, NULL, "Okay", NULL);
if(token_load_result == TokenLoadingResultWarning) {
dialog_message_set_text(
message,
"Unable to load some tokens\nPlease review conf file",
SCREEN_WIDTH_CENTER,
SCREEN_HEIGHT_CENTER,
AlignCenter,
AlignCenter);
} else if(token_load_result == TokenLoadingResultError) {
dialog_message_set_text(
message,
"Unable to load tokens\nPlease review conf file",
SCREEN_WIDTH_CENTER,
SCREEN_HEIGHT_CENTER,
AlignCenter,
AlignCenter);
}
dialog_message_show(plugin_state->dialogs_app, message);
dialog_message_free(message);
}
}
SceneState* scene_state = malloc(sizeof(SceneState));
furi_check(scene_state != 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;
}
scene_state->need_token_update = true;
plugin_state->current_scene_state = scene_state;
FURI_LOG_D(LOGGING_TAG, "Timezone set to: %f", (double)plugin_state->timezone_offset);
update_totp_params(plugin_state);
scene_state->type_code_worker_context = totp_type_code_worker_start();
scene_state->type_code_worker_context->string = &scene_state->last_code[0];
scene_state->type_code_worker_context->string_length = TOTP_TOKEN_DIGITS_MAX_COUNT + 1;
}
void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_state) {
if(plugin_state->tokens_count == 0) {
canvas_draw_str_aligned(
canvas,
SCREEN_WIDTH_CENTER,
SCREEN_HEIGHT_CENTER - 10,
AlignCenter,
AlignCenter,
"Token list is empty");
canvas_draw_str_aligned(
canvas,
SCREEN_WIDTH_CENTER,
SCREEN_HEIGHT_CENTER + 10,
AlignCenter,
AlignCenter,
"Press OK button to add");
return;
}
SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
FuriHalRtcDateTime curr_dt;
furi_hal_rtc_get_datetime(&curr_dt);
uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt);
bool is_new_token_time = curr_ts % TOKEN_LIFETIME == 0;
if(is_new_token_time && scene_state->last_token_gen_time != curr_ts) {
scene_state->need_token_update = true;
}
if(scene_state->need_token_update) {
scene_state->need_token_update = false;
scene_state->last_token_gen_time = curr_ts;
const TokenInfo* tokenInfo =
(TokenInfo*)(list_element_at(
plugin_state->tokens_list, scene_state->current_token_index)
->data);
if(tokenInfo->token != NULL && tokenInfo->token_length > 0) {
furi_mutex_acquire(
scene_state->type_code_worker_context->string_sync, FuriWaitForever);
size_t key_length;
uint8_t* key = totp_crypto_decrypt(
tokenInfo->token, tokenInfo->token_length, &plugin_state->iv[0], &key_length);
int_token_to_str(
totp_at(
get_totp_algo_impl(tokenInfo->algo),
tokenInfo->digits,
key,
key_length,
curr_ts,
plugin_state->timezone_offset,
TOKEN_LIFETIME),
scene_state->last_code,
tokenInfo->digits);
memset_s(key, key_length, 0, key_length);
free(key);
} else {
furi_mutex_acquire(
scene_state->type_code_worker_context->string_sync, FuriWaitForever);
int_token_to_str(0, scene_state->last_code, tokenInfo->digits);
}
furi_mutex_release(scene_state->type_code_worker_context->string_sync);
if(is_new_token_time) {
notification_message(
plugin_state->notification_app,
get_notification_sequence_new_token(plugin_state, scene_state));
}
}
canvas_set_font(canvas, FontPrimary);
uint16_t token_name_width = canvas_string_width(canvas, scene_state->last_code_name);
if(SCREEN_WIDTH - token_name_width > 18) {
canvas_draw_str_aligned(
canvas,
SCREEN_WIDTH_CENTER,
SCREEN_HEIGHT_CENTER - 20,
AlignCenter,
AlignCenter,
scene_state->last_code_name);
} else {
canvas_draw_str_aligned(
canvas,
9,
SCREEN_HEIGHT_CENTER - 20,
AlignLeft,
AlignCenter,
scene_state->last_code_name);
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 0, SCREEN_HEIGHT_CENTER - 24, 9, 9);
canvas_draw_box(canvas, SCREEN_WIDTH - 10, SCREEN_HEIGHT_CENTER - 24, 9, 9);
canvas_set_color(canvas, ColorBlack);
}
canvas_set_font(canvas, FontBigNumbers);
canvas_draw_str_aligned(
canvas,
SCREEN_WIDTH_CENTER,
SCREEN_HEIGHT_CENTER,
AlignCenter,
AlignCenter,
scene_state->last_code);
const uint8_t BAR_MARGIN = 3;
const uint8_t BAR_HEIGHT = 4;
float percentDone = (float)(TOKEN_LIFETIME - curr_ts % TOKEN_LIFETIME) / (float)TOKEN_LIFETIME;
uint8_t barWidth = (uint8_t)((float)(SCREEN_WIDTH - (BAR_MARGIN << 1)) * percentDone);
uint8_t barX = ((SCREEN_WIDTH - (BAR_MARGIN << 1) - barWidth) >> 1) + BAR_MARGIN;
canvas_draw_box(canvas, barX, SCREEN_HEIGHT - BAR_MARGIN - BAR_HEIGHT, barWidth, BAR_HEIGHT);
if(plugin_state->tokens_count > 1) {
canvas_draw_icon(canvas, 0, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_left_8x9);
canvas_draw_icon(
canvas, SCREEN_WIDTH - 9, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_right_8x9);
}
}
bool totp_scene_generate_token_handle_event(
const PluginEvent* const event,
PluginState* plugin_state) {
if(event->type != EventTypeKey) {
return true;
}
if(event->input.type == InputTypeLong && event->input.key == InputKeyBack) {
return false;
}
SceneState* scene_state;
if(event->input.type == InputTypeLong && event->input.key == InputKeyDown) {
scene_state = (SceneState*)plugin_state->current_scene_state;
totp_type_code_worker_notify(
scene_state->type_code_worker_context, TotpTypeCodeWorkerEventType);
notification_message(
plugin_state->notification_app,
get_notification_sequence_badusb(plugin_state, scene_state));
return true;
}
if(event->input.type != InputTypePress && event->input.type != InputTypeRepeat) {
return true;
}
scene_state = (SceneState*)plugin_state->current_scene_state;
switch(event->input.key) {
case InputKeyUp:
break;
case InputKeyDown:
break;
case InputKeyRight:
totp_roll_value_uint16_t(
&scene_state->current_token_index,
1,
0,
plugin_state->tokens_count - 1,
RollOverflowBehaviorRoll);
update_totp_params(plugin_state);
break;
case InputKeyLeft:
totp_roll_value_uint16_t(
&scene_state->current_token_index,
-1,
0,
plugin_state->tokens_count - 1,
RollOverflowBehaviorRoll);
update_totp_params(plugin_state);
break;
case InputKeyOk:
if(plugin_state->tokens_count == 0) {
totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL);
} else {
TokenMenuSceneContext ctx = {.current_token_index = scene_state->current_token_index};
totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, &ctx);
}
break;
case InputKeyBack:
break;
default:
break;
}
return true;
}
void totp_scene_generate_token_deactivate(PluginState* plugin_state) {
if(plugin_state->current_scene_state == NULL) return;
SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
totp_type_code_worker_stop(scene_state->type_code_worker_context);
if(scene_state->notification_sequence_new_token != NULL) {
free(scene_state->notification_sequence_new_token);
}
if(scene_state->notification_sequence_badusb != NULL) {
free(scene_state->notification_sequence_badusb);
}
free(scene_state);
plugin_state->current_scene_state = NULL;
}
void totp_scene_generate_token_free(const PluginState* plugin_state) {
UNUSED(plugin_state);
}
@@ -0,0 +1,20 @@
#pragma once
#include <gui/gui.h>
#include "../../../types/plugin_state.h"
#include "../../../types/plugin_event.h"
typedef struct {
uint16_t current_token_index;
} GenerateTokenSceneContext;
void totp_scene_generate_token_init(const PluginState* plugin_state);
void totp_scene_generate_token_activate(
PluginState* plugin_state,
const GenerateTokenSceneContext* context);
void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_state);
bool totp_scene_generate_token_handle_event(
const PluginEvent* const event,
PluginState* plugin_state);
void totp_scene_generate_token_deactivate(PluginState* plugin_state);
void totp_scene_generate_token_free(const PluginState* plugin_state);
@@ -0,0 +1,210 @@
#include "totp_scene_token_menu.h"
#include <gui/gui.h>
#include <dialogs/dialogs.h>
#include "../../ui_controls.h"
#include "../../common_dialogs.h"
#include "../../constants.h"
#include "../../scene_director.h"
#include "../../../services/config/config.h"
#include "../../../lib/list/list.h"
#include "../../../types/token_info.h"
#include "../generate_token/totp_scene_generate_token.h"
#include "../add_new_token/totp_scene_add_new_token.h"
#include "../app_settings/totp_app_settings.h"
#include "../../../types/nullable.h"
#include "../../../lib/roll_value/roll_value.h"
#define SCREEN_HEIGHT_THIRD (SCREEN_HEIGHT / 3)
#define SCREEN_HEIGHT_THIRD_CENTER (SCREEN_HEIGHT_THIRD >> 1)
typedef enum { AddNewToken, DeleteToken, AppSettings } Control;
typedef struct {
Control selected_control;
TotpNullable_uint16_t current_token_index;
} SceneState;
void totp_scene_token_menu_init(const PluginState* plugin_state) {
UNUSED(plugin_state);
}
void totp_scene_token_menu_activate(
PluginState* plugin_state,
const TokenMenuSceneContext* context) {
SceneState* scene_state = malloc(sizeof(SceneState));
furi_check(scene_state != NULL);
plugin_state->current_scene_state = scene_state;
if(context != NULL) {
TOTP_NULLABLE_VALUE(scene_state->current_token_index, context->current_token_index);
} else {
TOTP_NULLABLE_NULL(scene_state->current_token_index);
}
}
void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_state) {
const SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
if(scene_state->current_token_index.is_null) {
ui_control_button_render(
canvas,
SCREEN_WIDTH_CENTER - 36,
5,
72,
21,
"Add new token",
scene_state->selected_control == AddNewToken);
ui_control_button_render(
canvas,
SCREEN_WIDTH_CENTER - 36,
39,
72,
21,
"Settings",
scene_state->selected_control == AppSettings);
} else {
ui_control_button_render(
canvas,
SCREEN_WIDTH_CENTER - 36,
SCREEN_HEIGHT_THIRD_CENTER - 8,
72,
16,
"Add new token",
scene_state->selected_control == AddNewToken);
ui_control_button_render(
canvas,
SCREEN_WIDTH_CENTER - 36,
SCREEN_HEIGHT_THIRD + SCREEN_HEIGHT_THIRD_CENTER - 8,
72,
16,
"Delete token",
scene_state->selected_control == DeleteToken);
ui_control_button_render(
canvas,
SCREEN_WIDTH_CENTER - 36,
SCREEN_HEIGHT_THIRD + SCREEN_HEIGHT_THIRD + SCREEN_HEIGHT_THIRD_CENTER - 8,
72,
16,
"Settings",
scene_state->selected_control == AppSettings);
}
}
bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginState* plugin_state) {
if(event->type != EventTypeKey) {
return true;
}
SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
if(event->input.type != InputTypePress) {
return true;
}
switch(event->input.key) {
case InputKeyUp:
totp_roll_value_uint8_t(
&scene_state->selected_control, -1, AddNewToken, AppSettings, RollOverflowBehaviorRoll);
if(scene_state->selected_control == DeleteToken &&
scene_state->current_token_index.is_null) {
scene_state->selected_control--;
}
break;
case InputKeyDown:
totp_roll_value_uint8_t(
&scene_state->selected_control, 1, AddNewToken, AppSettings, RollOverflowBehaviorRoll);
if(scene_state->selected_control == DeleteToken &&
scene_state->current_token_index.is_null) {
scene_state->selected_control++;
}
break;
case InputKeyRight:
break;
case InputKeyLeft:
break;
case InputKeyOk:
switch(scene_state->selected_control) {
case AddNewToken: {
if(scene_state->current_token_index.is_null) {
totp_scene_director_activate_scene(plugin_state, TotpSceneAddNewToken, NULL);
} else {
TokenAddEditSceneContext add_new_token_scene_context = {
.current_token_index = scene_state->current_token_index.value};
totp_scene_director_activate_scene(
plugin_state, TotpSceneAddNewToken, &add_new_token_scene_context);
}
break;
}
case DeleteToken: {
DialogMessage* message = dialog_message_alloc();
dialog_message_set_buttons(message, "No", NULL, "Yes");
dialog_message_set_header(message, "Confirmation", 0, 0, AlignLeft, AlignTop);
dialog_message_set_text(
message,
"Are you sure want to delete?",
SCREEN_WIDTH_CENTER,
SCREEN_HEIGHT_CENTER,
AlignCenter,
AlignCenter);
DialogMessageButton dialog_result =
dialog_message_show(plugin_state->dialogs_app, message);
dialog_message_free(message);
if(dialog_result == DialogMessageButtonRight &&
!scene_state->current_token_index.is_null) {
TokenInfo* tokenInfo = NULL;
plugin_state->tokens_list = list_remove_at(
plugin_state->tokens_list,
scene_state->current_token_index.value,
(void**)&tokenInfo);
plugin_state->tokens_count--;
furi_check(tokenInfo != NULL);
token_info_free(tokenInfo);
if(totp_full_save_config_file(plugin_state) != TotpConfigFileUpdateSuccess) {
totp_dialogs_config_updating_error(plugin_state);
return false;
}
totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
}
break;
}
case AppSettings: {
if(!scene_state->current_token_index.is_null) {
AppSettingsSceneContext app_settings_context = {
.current_token_index = scene_state->current_token_index.value};
totp_scene_director_activate_scene(
plugin_state, TotpSceneAppSettings, &app_settings_context);
} else {
totp_scene_director_activate_scene(plugin_state, TotpSceneAppSettings, NULL);
}
break;
}
default:
break;
}
break;
case InputKeyBack: {
if(!scene_state->current_token_index.is_null) {
GenerateTokenSceneContext generate_scene_context = {
.current_token_index = scene_state->current_token_index.value};
totp_scene_director_activate_scene(
plugin_state, TotpSceneGenerateToken, &generate_scene_context);
} else {
totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
}
break;
}
default:
break;
}
return true;
}
void totp_scene_token_menu_deactivate(PluginState* plugin_state) {
if(plugin_state->current_scene_state == NULL) return;
free(plugin_state->current_scene_state);
plugin_state->current_scene_state = NULL;
}
void totp_scene_token_menu_free(const PluginState* plugin_state) {
UNUSED(plugin_state);
}
@@ -0,0 +1,18 @@
#pragma once
#include <gui/gui.h>
#include "../../../types/plugin_state.h"
#include "../../../types/plugin_event.h"
typedef struct {
uint16_t current_token_index;
} TokenMenuSceneContext;
void totp_scene_token_menu_init(const PluginState* plugin_state);
void totp_scene_token_menu_activate(
PluginState* plugin_state,
const TokenMenuSceneContext* context);
void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_state);
bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginState* plugin_state);
void totp_scene_token_menu_deactivate(PluginState* plugin_state);
void totp_scene_token_menu_free(const PluginState* plugin_state);
@@ -0,0 +1,38 @@
#pragma once
typedef uint8_t Scene;
/**
* @brief TOTP application scenes
*/
enum Scenes {
/**
* @brief Empty scene which does nothing
*/
TotpSceneNone,
/**
* @brief Scene where user have to enter PIN to authenticate
*/
TotpSceneAuthentication,
/**
* @brief Scene where actual TOTP token is getting generated and displayed to the user
*/
TotpSceneGenerateToken,
/**
* @brief Scene where user can add new token
*/
TotpSceneAddNewToken,
/**
* @brief Scene with a menu for given token, allowing user to do multiple actions
*/
TotpSceneTokenMenu,
/**
* @brief Scene where user can change application settings
*/
TotpSceneAppSettings
};
@@ -0,0 +1,114 @@
#include "ui_controls.h"
#include <totp_icons.h>
#include "constants.h"
#define TEXT_BOX_HEIGHT 13
#define TEXT_BOX_MARGIN 4
void ui_control_text_box_render(
Canvas* const canvas,
int16_t y,
const char* text,
bool is_selected) {
if(y < -TEXT_BOX_HEIGHT) {
return;
}
if(is_selected) {
canvas_draw_rframe(
canvas,
TEXT_BOX_MARGIN,
TEXT_BOX_MARGIN + y,
SCREEN_WIDTH - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN,
TEXT_BOX_HEIGHT,
0);
canvas_draw_rframe(
canvas,
TEXT_BOX_MARGIN - 1,
TEXT_BOX_MARGIN + y - 1,
SCREEN_WIDTH - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN + 2,
TEXT_BOX_HEIGHT + 2,
1);
} else {
canvas_draw_rframe(
canvas,
TEXT_BOX_MARGIN,
TEXT_BOX_MARGIN + y,
SCREEN_WIDTH - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN,
TEXT_BOX_HEIGHT,
1);
}
canvas_draw_str_aligned(
canvas, TEXT_BOX_MARGIN + 2, TEXT_BOX_MARGIN + 3 + y, AlignLeft, AlignTop, text);
}
void ui_control_select_render(
Canvas* const canvas,
int16_t x,
int16_t y,
uint8_t width,
const char* text,
bool is_selected) {
if(y < -TEXT_BOX_HEIGHT) {
return;
}
if(is_selected) {
canvas_draw_rframe(
canvas,
x + TEXT_BOX_MARGIN,
TEXT_BOX_MARGIN + y,
width - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN,
TEXT_BOX_HEIGHT,
0);
canvas_draw_rframe(
canvas,
x + TEXT_BOX_MARGIN - 1,
TEXT_BOX_MARGIN + y - 1,
width - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN + 2,
TEXT_BOX_HEIGHT + 2,
1);
} else {
canvas_draw_rframe(
canvas,
x + TEXT_BOX_MARGIN,
TEXT_BOX_MARGIN + y,
width - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN,
TEXT_BOX_HEIGHT,
1);
}
canvas_draw_str_aligned(
canvas, x + (width >> 1), TEXT_BOX_MARGIN + 3 + y, AlignCenter, AlignTop, text);
canvas_draw_icon(
canvas, x + TEXT_BOX_MARGIN + 2, TEXT_BOX_MARGIN + 2 + y, &I_totp_arrow_left_8x9);
canvas_draw_icon(
canvas, x + width - TEXT_BOX_MARGIN - 10, TEXT_BOX_MARGIN + 2 + y, &I_totp_arrow_right_8x9);
}
void ui_control_button_render(
Canvas* const canvas,
int16_t x,
int16_t y,
uint8_t width,
uint8_t height,
const char* text,
bool is_selected) {
if(y < -height) {
return;
}
if(is_selected) {
canvas_draw_rbox(canvas, x, y, width, height, 1);
canvas_set_color(canvas, ColorWhite);
} else {
canvas_draw_rframe(canvas, x, y, width, height, 1);
}
canvas_draw_str_aligned(
canvas, x + (width >> 1), y + (height >> 1) + 1, AlignCenter, AlignCenter, text);
if(is_selected) {
canvas_set_color(canvas, ColorBlack);
}
}
@@ -0,0 +1,53 @@
#pragma once
#include <inttypes.h>
#include <gui/gui.h>
/**
* @brief Renders TextBox control
* @param canvas canvas to render control at
* @param y vertical position of a control to be rendered at
* @param text text to be rendered inside control
* @param is_selected whether control should be rendered as focused or not
*/
void ui_control_text_box_render(
Canvas* const canvas,
int16_t y,
const char* text,
bool is_selected);
/**
* @brief Renders Button control
* @param canvas canvas to render control at
* @param x horizontal position of a control to be rendered at
* @param y vertical position of a control to be rendered at
* @param width control width
* @param height control height
* @param text text to be rendered inside control
* @param is_selected whether control should be rendered as focused or not
*/
void ui_control_button_render(
Canvas* const canvas,
int16_t x,
int16_t y,
uint8_t width,
uint8_t height,
const char* text,
bool is_selected);
/**
* @brief Renders Select control
* @param canvas canvas to render control at
* @param x horizontal position of a control to be rendered at
* @param y vertical position of a control to be rendered at
* @param width control width
* @param text text to be rendered inside control
* @param is_selected whether control should be rendered as focused or not
*/
void ui_control_select_render(
Canvas* const canvas,
int16_t x,
int16_t y,
uint8_t width,
const char* text,
bool is_selected);
@@ -0,0 +1,115 @@
#include "type_code.h"
#include "../../services/convert/convert.h"
static const uint8_t hid_number_keys[10] = {
HID_KEYBOARD_0,
HID_KEYBOARD_1,
HID_KEYBOARD_2,
HID_KEYBOARD_3,
HID_KEYBOARD_4,
HID_KEYBOARD_5,
HID_KEYBOARD_6,
HID_KEYBOARD_7,
HID_KEYBOARD_8,
HID_KEYBOARD_9};
static void totp_type_code_worker_restore_usb_mode(TotpTypeCodeWorkerContext* context) {
if(context->usb_mode_prev != NULL) {
furi_hal_usb_set_config(context->usb_mode_prev, NULL);
context->usb_mode_prev = NULL;
}
}
static inline bool totp_type_code_worker_stop_requested() {
return furi_thread_flags_get() & TotpTypeCodeWorkerEventStop;
}
static void totp_type_code_worker_type_code(TotpTypeCodeWorkerContext* context) {
context->usb_mode_prev = furi_hal_usb_get_config();
furi_hal_usb_unlock();
furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true);
uint8_t i = 0;
do {
furi_delay_ms(500);
i++;
} while(!furi_hal_hid_is_connected() && i < 100 && !totp_type_code_worker_stop_requested());
if(furi_hal_hid_is_connected() &&
furi_mutex_acquire(context->string_sync, 500) == FuriStatusOk) {
furi_delay_ms(500);
i = 0;
while(i < context->string_length && context->string[i] != 0) {
uint8_t digit = CONVERT_CHAR_TO_DIGIT(context->string[i]);
if(digit > 9) break;
uint8_t hid_kb_key = hid_number_keys[digit];
furi_hal_hid_kb_press(hid_kb_key);
furi_delay_ms(30);
furi_hal_hid_kb_release(hid_kb_key);
i++;
}
furi_mutex_release(context->string_sync);
furi_delay_ms(100);
}
totp_type_code_worker_restore_usb_mode(context);
}
static int32_t totp_type_code_worker_callback(void* context) {
ValueMutex context_mutex;
if(!init_mutex(&context_mutex, context, sizeof(TotpTypeCodeWorkerContext))) {
return 251;
}
while(true) {
uint32_t flags = furi_thread_flags_wait(
TotpTypeCodeWorkerEventStop | TotpTypeCodeWorkerEventType,
FuriFlagWaitAny,
FuriWaitForever);
furi_check((flags & FuriFlagError) == 0); //-V562
if(flags & TotpTypeCodeWorkerEventStop) break;
TotpTypeCodeWorkerContext* h_context = acquire_mutex_block(&context_mutex);
if(flags & TotpTypeCodeWorkerEventType) {
totp_type_code_worker_type_code(h_context);
}
release_mutex(&context_mutex, h_context);
}
delete_mutex(&context_mutex);
return 0;
}
TotpTypeCodeWorkerContext* totp_type_code_worker_start() {
TotpTypeCodeWorkerContext* context = malloc(sizeof(TotpTypeCodeWorkerContext));
furi_check(context != NULL);
context->string_sync = furi_mutex_alloc(FuriMutexTypeNormal);
context->thread = furi_thread_alloc();
context->usb_mode_prev = NULL;
furi_thread_set_name(context->thread, "TOTPHidWorker");
furi_thread_set_stack_size(context->thread, 1024);
furi_thread_set_context(context->thread, context);
furi_thread_set_callback(context->thread, totp_type_code_worker_callback);
furi_thread_start(context->thread);
return context;
}
void totp_type_code_worker_stop(TotpTypeCodeWorkerContext* context) {
furi_assert(context != NULL);
furi_thread_flags_set(furi_thread_get_id(context->thread), TotpTypeCodeWorkerEventStop);
furi_thread_join(context->thread);
furi_thread_free(context->thread);
furi_mutex_free(context->string_sync);
totp_type_code_worker_restore_usb_mode(context);
free(context);
}
void totp_type_code_worker_notify(
TotpTypeCodeWorkerContext* context,
TotpTypeCodeWorkerEvent event) {
furi_assert(context != NULL);
furi_thread_flags_set(furi_thread_get_id(context->thread), event);
}
@@ -0,0 +1,27 @@
#pragma once
#include <stdlib.h>
#include <furi/furi.h>
#include <furi_hal.h>
typedef uint8_t TotpTypeCodeWorkerEvent;
typedef struct {
char* string;
uint8_t string_length;
FuriThread* thread;
FuriMutex* string_sync;
FuriHalUsbInterface* usb_mode_prev;
} TotpTypeCodeWorkerContext;
enum TotpTypeCodeWorkerEvents {
TotpTypeCodeWorkerEventReserved = (1 << 0),
TotpTypeCodeWorkerEventStop = (1 << 1),
TotpTypeCodeWorkerEventType = (1 << 2)
};
TotpTypeCodeWorkerContext* totp_type_code_worker_start();
void totp_type_code_worker_stop(TotpTypeCodeWorkerContext* context);
void totp_type_code_worker_notify(
TotpTypeCodeWorkerContext* context,
TotpTypeCodeWorkerEvent event);