diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000000..4b76f7fa43b --- /dev/null +++ b/.clang-format @@ -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 +... + diff --git a/application.fam b/application.fam new file mode 100644 index 00000000000..abcb8a4062e --- /dev/null +++ b/application.fam @@ -0,0 +1,26 @@ +App( + appid="totp", + name="Authenticator", + apptype=FlipperAppType.EXTERNAL, + entry_point="totp_app", + cdefines=["APP_TOTP"], + requires=[ + "gui", + "cli", + "dialogs", + "storage" + ], + provides=["totp_start"], + stack_size=2 * 1024, + order=20, + fap_category="Misc", + fap_icon="totp_10px.png", +) + +App( + appid="totp_start", + apptype=FlipperAppType.STARTUP, + entry_point="totp_on_system_start", + requires=["totp"], + order=30, +) diff --git a/lib/base32/base32.c b/lib/base32/base32.c new file mode 100644 index 00000000000..9c8e302360f --- /dev/null +++ b/lib/base32/base32.c @@ -0,0 +1,95 @@ +// 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 + +#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; +} + +int base32_encode(const uint8_t *data, int length, uint8_t *result, + int bufSize) { + if (length < 0 || length > (1 << 28)) { + return -1; + } + int count = 0; + if (length > 0) { + int buffer = data[0]; + int next = 1; + int bitsLeft = 8; + while (count < bufSize && (bitsLeft > 0 || next < length)) { + if (bitsLeft < 5) { + if (next < length) { + buffer <<= 8; + buffer |= data[next++] & 0xFF; + bitsLeft += 8; + } else { + int pad = 5 - bitsLeft; + buffer <<= pad; + bitsLeft += pad; + } + } + int index = 0x1F & (buffer >> (bitsLeft - 5)); + bitsLeft -= 5; + result[count++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[index]; + } + } + if (count < bufSize) { + result[count] = '\000'; + } + return count; +} diff --git a/lib/base32/base32.h b/lib/base32/base32.h new file mode 100644 index 00000000000..fb4da0bee5e --- /dev/null +++ b/lib/base32/base32.h @@ -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. + +#ifndef _BASE32_H_ +#define _BASE32_H_ + +#include + +int base32_decode(const uint8_t *encoded, uint8_t *result, int bufSize) + __attribute__((visibility("hidden"))); +int base32_encode(const uint8_t *data, int length, uint8_t *result, + int bufSize) + __attribute__((visibility("hidden"))); + +#endif /* _BASE32_H_ */ diff --git a/lib/config/config.c b/lib/config/config.c new file mode 100644 index 00000000000..1d5e3557ee2 --- /dev/null +++ b/lib/config/config.c @@ -0,0 +1,331 @@ +#include "config.h" +#include +#include +#include "../../types/common.h" +#include "../../types/token_info.h" +#include "migrations/config_migration_v1_to_v2.h" + +#define CONFIG_FILE_DIRECTORY_PATH "/ext/apps/Misc" +#define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/totp.conf" +#define CONFIG_FILE_BACKUP_PATH CONFIG_FILE_PATH ".backup" + +uint8_t token_info_get_digits_as_int(TokenInfo* token_info) { + switch (token_info->digits) { + case TOTP_6_DIGITS: return 6; + case TOTP_8_DIGITS: return 8; + } + + return 6; +} + +void token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits) { + switch (digits) { + case 6: + token_info->digits = TOTP_6_DIGITS; + break; + case 8: + token_info->digits = TOTP_8_DIGITS; + break; + } +} + +char* token_info_get_algo_as_cstr(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; + } + + return NULL; +} + +void token_info_set_algo_from_str(TokenInfo* token_info, string_t str) { + if (string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME) == 0) { + token_info->algo = SHA1; + } else if (string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME)) { + token_info->algo = SHA256; + } else if (string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME)) { + token_info->algo = SHA512; + } +} + +Storage* totp_open_storage() { + return furi_record_open(RECORD_STORAGE); +} + +void totp_close_storage() { + furi_record_close(RECORD_STORAGE); +} + +FlipperFormat* totp_open_config_file(Storage* storage) { + 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 NULL; + } + } 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 NULL; + } + } + + 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 NULL; + } + + 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); + string_t temp_str; + string_init(temp_str); + + 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."); + 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"); + 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, " "); + + 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); + 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"); + 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, " "); + + string_clear(temp_str); + if(!flipper_format_rewind(fff_data_file)) { + totp_close_config_file(fff_data_file); + FURI_LOG_E(LOGGING_TAG, "Rewind error"); + return NULL; + } + } + + return fff_data_file; +} + +void totp_config_file_save_new_token(FlipperFormat* file, TokenInfo* token_info) { + flipper_format_write_string_cstr(file, TOTP_CONFIG_KEY_TOKEN_NAME, token_info->name); + flipper_format_write_hex(file, TOTP_CONFIG_KEY_TOKEN_SECRET, token_info->token, token_info->token_length); + flipper_format_write_string_cstr(file, TOTP_CONFIG_KEY_TOKEN_ALGO, token_info_get_algo_as_cstr(token_info)); + uint32_t digits_count_as_uint32 = token_info_get_digits_as_int(token_info); + flipper_format_write_uint32(file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &digits_count_as_uint32, 1); +} + +void totp_full_save_config_file(PluginState* const plugin_state) { + Storage* storage = totp_open_storage(); + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + + flipper_format_file_open_always(fff_data_file, CONFIG_FILE_PATH); + flipper_format_write_header_cstr(fff_data_file, CONFIG_FILE_HEADER, CONFIG_FILE_ACTUAL_VERSION); + flipper_format_write_hex(fff_data_file, TOTP_CONFIG_KEY_BASE_IV, &plugin_state->base_iv[0], TOTP_IV_SIZE); + flipper_format_write_hex(fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, plugin_state->crypto_verify_data, plugin_state->crypto_verify_data_length); + flipper_format_write_float(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1); + ListNode* node = plugin_state->tokens_list; + while (node != NULL) { + TokenInfo* token_info = node->data; + totp_config_file_save_new_token(fff_data_file, token_info); + node = node->next; + } + + totp_close_config_file(fff_data_file); + totp_close_storage(); +} + +void totp_config_file_load_base(PluginState* const plugin_state) { + Storage* storage = totp_open_storage(); + FlipperFormat* fff_data_file = totp_open_config_file(storage); + + plugin_state->timezone_offset = 0; + + string_t temp_str; + string_init(temp_str); + + 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"); + string_clear(temp_str); + return; + } + + if (file_version < CONFIG_FILE_ACTUAL_VERSION) { + FURI_LOG_I(LOGGING_TAG, "Obsolete config file version detected. Current version: %d; Actual version: %d", 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); + fff_data_file = totp_open_config_file(storage); + FlipperFormat* fff_backup_data_file = flipper_format_file_alloc(storage); + flipper_format_file_open_existing(fff_backup_data_file, CONFIG_FILE_BACKUP_PATH); + + 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"); + } + } + + 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); + } + } + + 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"); + } + + flipper_format_rewind(fff_data_file); + + uint32_t crypto_size; + if (flipper_format_get_value_count(fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, &crypto_size)) { + plugin_state->crypto_verify_data = malloc(sizeof(uint8_t) * crypto_size); + 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; + } + } + + flipper_format_rewind(fff_data_file); + + 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"); + } + + string_clear(temp_str); + totp_close_config_file(fff_data_file); + totp_close_storage(); +} + +void totp_config_file_load_tokens(PluginState* const plugin_state) { + Storage* storage = totp_open_storage(); + FlipperFormat* fff_data_file = totp_open_config_file(storage); + + string_t temp_str; + uint32_t temp_data32; + string_init(temp_str); + + if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) { + FURI_LOG_E(LOGGING_TAG, "Missing or incorrect header"); + string_clear(temp_str); + return; + } + + uint8_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(); + + const char* temp_cstr = string_get_cstr(temp_str); + tokenInfo->name = (char *)malloc(strlen(temp_cstr) + 1); + strcpy(tokenInfo->name, temp_cstr); + + uint32_t secret_bytes_count; + if (!flipper_format_get_value_count(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, &secret_bytes_count)) { + token_info_free(tokenInfo); + continue; + } + + if (secret_bytes_count == 1) { // Plain secret key + if (!flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str)) { + token_info_free(tokenInfo); + continue; + } + + temp_cstr = string_get_cstr(temp_str); + token_info_set_secret(tokenInfo, temp_cstr, strlen(temp_cstr), &plugin_state->iv[0]); + has_any_plain_secret = true; + FURI_LOG_W(LOGGING_TAG, "Found token with plain secret"); + } else { // encrypted + tokenInfo->token_length = secret_bytes_count; + tokenInfo->token = malloc(tokenInfo->token_length); + if (!flipper_format_read_hex(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, tokenInfo->token, tokenInfo->token_length)) { + token_info_free(tokenInfo); + continue; + } + } + + if (!flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str)) { + token_info_free(tokenInfo); + continue; + } + + token_info_set_algo_from_str(tokenInfo, temp_str); + + if (!flipper_format_read_uint32(fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &temp_data32, 1)) { + token_info_free(tokenInfo); + continue; + } + + token_info_set_digits_from_int(tokenInfo, temp_data32); + + FURI_LOG_D(LOGGING_TAG, "Found token \"%s\"", tokenInfo->name); + + if (plugin_state->tokens_list == NULL) { + plugin_state->tokens_list = list_init_head(tokenInfo); + } else { + list_add(plugin_state->tokens_list, tokenInfo); + } + + index++; + } + + plugin_state->tokens_count = index; + plugin_state->token_list_loaded = true; + + FURI_LOG_D(LOGGING_TAG, "Found %d tokens", index); + + string_clear(temp_str); + totp_close_config_file(fff_data_file); + totp_close_storage(); + + if (has_any_plain_secret) { + totp_full_save_config_file(plugin_state); + } +} + +void totp_close_config_file(FlipperFormat* file) { + if (file == NULL) return; + flipper_format_file_close(file); + flipper_format_free(file); +} diff --git a/lib/config/config.h b/lib/config/config.h new file mode 100644 index 00000000000..43b6e01bd84 --- /dev/null +++ b/lib/config/config.h @@ -0,0 +1,19 @@ +#ifndef _TOTP_CONFIG_FILE_H_ +#define _TOTP_CONFIG_FILE_H_ + +#include +#include +#include "../../types/plugin_state.h" +#include "../../types/token_info.h" +#include "constants.h" + +Storage* totp_open_storage(); +void totp_close_storage(); +FlipperFormat* totp_open_config_file(Storage* storage); +void totp_close_config_file(FlipperFormat* file); +void totp_full_save_config_file(PluginState* const plugin_state); +void totp_config_file_load_base(PluginState* const plugin_state); +void totp_config_file_load_tokens(PluginState* const plugin_state); +void totp_config_file_save_new_token(FlipperFormat* file, TokenInfo* token_info); + +#endif diff --git a/lib/config/constants.h b/lib/config/constants.h new file mode 100644 index 00000000000..5ca92a71046 --- /dev/null +++ b/lib/config/constants.h @@ -0,0 +1,19 @@ +#ifndef _TOTP_CONFIG_CONSTANTS_FILE_H_ +#define _TOTP_CONFIG_CONSTANTS_FILE_H_ + +#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_TOKEN_ALGO_SHA1_NAME "sha1" +#define TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME "sha256" +#define TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME "sha512" + +#endif diff --git a/lib/config/migrations/config_migration_v1_to_v2.c b/lib/config/migrations/config_migration_v1_to_v2.c new file mode 100644 index 00000000000..fd5697a4ea5 --- /dev/null +++ b/lib/config/migrations/config_migration_v1_to_v2.c @@ -0,0 +1,42 @@ +#include "config_migration_v1_to_v2.h" +#include +#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); + + string_t temp_str; + string_init(temp_str); + + 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); + } + + string_clear(temp_str); + return true; +} diff --git a/lib/config/migrations/config_migration_v1_to_v2.h b/lib/config/migrations/config_migration_v1_to_v2.h new file mode 100644 index 00000000000..2dc1106f40f --- /dev/null +++ b/lib/config/migrations/config_migration_v1_to_v2.h @@ -0,0 +1,8 @@ +#ifndef _TOTP_CONFIG_FILE_MIGRATE_V1_TO_V2_H_ +#define _TOTP_CONFIG_FILE_MIGRATE_V1_TO_V2_H_ + +#include + +bool totp_config_migrate_v1_to_v2(FlipperFormat* fff_data_file, FlipperFormat* fff_backup_data_file); + +#endif diff --git a/lib/hmac/byteswap.c b/lib/hmac/byteswap.c new file mode 100644 index 00000000000..2a649fe640a --- /dev/null +++ b/lib/hmac/byteswap.c @@ -0,0 +1,14 @@ +#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); +} diff --git a/lib/hmac/byteswap.h b/lib/hmac/byteswap.h new file mode 100644 index 00000000000..c412e4cd6d8 --- /dev/null +++ b/lib/hmac/byteswap.h @@ -0,0 +1,9 @@ +#ifndef BYTESWAP_H +#define BYTESWAP_H + +#include + +uint32_t swap_uint32( uint32_t val ); +uint64_t swap_uint64( uint64_t val ); + +#endif diff --git a/lib/hmac/hmac-common.h b/lib/hmac/hmac-common.h new file mode 100644 index 00000000000..cb6693a1635 --- /dev/null +++ b/lib/hmac/hmac-common.h @@ -0,0 +1,71 @@ +#include +#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; +} diff --git a/lib/hmac/hmac-sha1.c b/lib/hmac/hmac-sha1.c new file mode 100644 index 00000000000..15df462ce44 --- /dev/null +++ b/lib/hmac/hmac-sha1.c @@ -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 . */ + +#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" diff --git a/lib/hmac/hmac-sha1.h b/lib/hmac/hmac-sha1.h new file mode 100644 index 00000000000..5bf0b8157dd --- /dev/null +++ b/lib/hmac/hmac-sha1.h @@ -0,0 +1,14 @@ +#ifndef HMAC_SHA1_H +#define HMAC_SHA1_H + +#include + +#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); + +#endif /* HMAC_SHA1_H */ diff --git a/lib/hmac/hmac-sha256.c b/lib/hmac/hmac-sha256.c new file mode 100644 index 00000000000..85434c6feeb --- /dev/null +++ b/lib/hmac/hmac-sha256.c @@ -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 . */ + +#include "hmac-sha256.h" + +#define GL_HMAC_NAME 256 +#define GL_HMAC_BLOCKSIZE 64 +#define GL_HMAC_HASHSIZE 32 + +#include "hmac-common.h" diff --git a/lib/hmac/hmac-sha256.h b/lib/hmac/hmac-sha256.h new file mode 100644 index 00000000000..a46fb89b716 --- /dev/null +++ b/lib/hmac/hmac-sha256.h @@ -0,0 +1,14 @@ +#ifndef HMAC_SHA256_H +#define HMAC_SHA256_H + +#include + +#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); + +#endif /* HMAC_SHA256_H */ diff --git a/lib/hmac/hmac-sha512.c b/lib/hmac/hmac-sha512.c new file mode 100644 index 00000000000..85cda13f521 --- /dev/null +++ b/lib/hmac/hmac-sha512.c @@ -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 . */ + +#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" diff --git a/lib/hmac/hmac-sha512.h b/lib/hmac/hmac-sha512.h new file mode 100644 index 00000000000..8ad7d6ff0a1 --- /dev/null +++ b/lib/hmac/hmac-sha512.h @@ -0,0 +1,14 @@ +#ifndef HMAC_SHA512_H +#define HMAC_SHA512_H + +#include + +#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); + +#endif /* HMAC_SHA512_H */ diff --git a/lib/hmac/memxor.c b/lib/hmac/memxor.c new file mode 100644 index 00000000000..60fc7529a41 --- /dev/null +++ b/lib/hmac/memxor.c @@ -0,0 +1,34 @@ +/* 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 */ + +#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; +} diff --git a/lib/hmac/memxor.h b/lib/hmac/memxor.h new file mode 100644 index 00000000000..e67534e31f1 --- /dev/null +++ b/lib/hmac/memxor.h @@ -0,0 +1,31 @@ +/* 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. */ + +#ifndef MEMXOR_H +# define MEMXOR_H + +#include + +/* 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); + +#endif /* MEMXOR_H */ diff --git a/lib/hmac/sha1.c b/lib/hmac/sha1.c new file mode 100644 index 00000000000..5913271da8f --- /dev/null +++ b/lib/hmac/sha1.c @@ -0,0 +1,358 @@ +/* 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 . */ + +/* Written by Scott G. Miller + Credits: + Robert Klep -- Expansion function fix +*/ + +/* Specification. */ +#if HAVE_OPENSSL_SHA1 +# define GL_OPENSSL_INLINE _GL_EXTERN_INLINE +#endif +#include "sha1.h" + +#include +#include + +#ifdef WORDS_BIGENDIAN +# define SWAP(n) (n) +#else +#include "byteswap.h" +# define SWAP(n) swap_uint32(n) +#endif + +#if ! HAVE_OPENSSL_SHA1 + +/* 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); + 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++; + } + + R( a, b, c, d, e, F1, K1, x[ 0] ); + R( e, a, b, c, d, F1, K1, x[ 1] ); + R( d, e, a, b, c, F1, K1, x[ 2] ); + R( c, d, e, a, b, F1, K1, x[ 3] ); + R( b, c, d, e, a, F1, K1, x[ 4] ); + R( a, b, c, d, e, F1, K1, x[ 5] ); + R( e, a, b, c, d, F1, K1, x[ 6] ); + R( d, e, a, b, c, F1, K1, x[ 7] ); + R( c, d, e, a, b, F1, K1, x[ 8] ); + R( b, c, d, e, a, F1, K1, x[ 9] ); + R( a, b, c, d, e, F1, K1, x[10] ); + R( e, a, b, c, d, F1, K1, x[11] ); + R( d, e, a, b, c, F1, K1, x[12] ); + R( c, d, e, a, b, F1, K1, x[13] ); + R( b, c, d, e, a, F1, K1, x[14] ); + R( a, b, c, d, e, F1, K1, x[15] ); + R( e, a, b, c, d, F1, K1, M(16) ); + R( d, e, a, b, c, F1, K1, M(17) ); + R( c, d, e, a, b, F1, K1, M(18) ); + R( b, c, d, e, a, F1, K1, M(19) ); + R( a, b, c, d, e, F2, K2, M(20) ); + R( e, a, b, c, d, F2, K2, M(21) ); + R( d, e, a, b, c, F2, K2, M(22) ); + R( c, d, e, a, b, F2, K2, M(23) ); + R( b, c, d, e, a, F2, K2, M(24) ); + R( a, b, c, d, e, F2, K2, M(25) ); + R( e, a, b, c, d, F2, K2, M(26) ); + R( d, e, a, b, c, F2, K2, M(27) ); + R( c, d, e, a, b, F2, K2, M(28) ); + R( b, c, d, e, a, F2, K2, M(29) ); + R( a, b, c, d, e, F2, K2, M(30) ); + R( e, a, b, c, d, F2, K2, M(31) ); + R( d, e, a, b, c, F2, K2, M(32) ); + R( c, d, e, a, b, F2, K2, M(33) ); + R( b, c, d, e, a, F2, K2, M(34) ); + R( a, b, c, d, e, F2, K2, M(35) ); + R( e, a, b, c, d, F2, K2, M(36) ); + R( d, e, a, b, c, F2, K2, M(37) ); + R( c, d, e, a, b, F2, K2, M(38) ); + R( b, c, d, e, a, F2, K2, M(39) ); + R( a, b, c, d, e, F3, K3, M(40) ); + R( e, a, b, c, d, F3, K3, M(41) ); + R( d, e, a, b, c, F3, K3, M(42) ); + R( c, d, e, a, b, F3, K3, M(43) ); + R( b, c, d, e, a, F3, K3, M(44) ); + R( a, b, c, d, e, F3, K3, M(45) ); + R( e, a, b, c, d, F3, K3, M(46) ); + R( d, e, a, b, c, F3, K3, M(47) ); + R( c, d, e, a, b, F3, K3, M(48) ); + R( b, c, d, e, a, F3, K3, M(49) ); + R( a, b, c, d, e, F3, K3, M(50) ); + R( e, a, b, c, d, F3, K3, M(51) ); + R( d, e, a, b, c, F3, K3, M(52) ); + R( c, d, e, a, b, F3, K3, M(53) ); + R( b, c, d, e, a, F3, K3, M(54) ); + R( a, b, c, d, e, F3, K3, M(55) ); + R( e, a, b, c, d, F3, K3, M(56) ); + R( d, e, a, b, c, F3, K3, M(57) ); + R( c, d, e, a, b, F3, K3, M(58) ); + R( b, c, d, e, a, F3, K3, M(59) ); + R( a, b, c, d, e, F4, K4, M(60) ); + R( e, a, b, c, d, F4, K4, M(61) ); + R( d, e, a, b, c, F4, K4, M(62) ); + R( c, d, e, a, b, F4, K4, M(63) ); + R( b, c, d, e, a, F4, K4, M(64) ); + R( a, b, c, d, e, F4, K4, M(65) ); + R( e, a, b, c, d, F4, K4, M(66) ); + R( d, e, a, b, c, F4, K4, M(67) ); + R( c, d, e, a, b, F4, K4, M(68) ); + R( b, c, d, e, a, F4, K4, M(69) ); + R( a, b, c, d, e, F4, K4, M(70) ); + R( e, a, b, c, d, F4, K4, M(71) ); + R( d, e, a, b, c, F4, K4, M(72) ); + R( c, d, e, a, b, F4, K4, M(73) ); + R( b, c, d, e, a, F4, K4, M(74) ); + R( a, b, c, d, e, F4, K4, M(75) ); + R( e, a, b, c, d, F4, K4, M(76) ); + R( d, e, a, b, c, F4, K4, M(77) ); + R( c, d, e, a, b, F4, K4, M(78) ); + R( b, c, d, e, a, F4, K4, M(79) ); + + a = ctx->A += a; + b = ctx->B += b; + c = ctx->C += c; + d = ctx->D += d; + e = ctx->E += e; + } +} + +#endif + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/lib/hmac/sha1.h b/lib/hmac/sha1.h new file mode 100644 index 00000000000..bc3470a508d --- /dev/null +++ b/lib/hmac/sha1.h @@ -0,0 +1,115 @@ +/* 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 . */ + +#ifndef SHA1_H +# define SHA1_H 1 + +# include +# include + +# if HAVE_OPENSSL_SHA1 +# ifndef OPENSSL_API_COMPAT +# define OPENSSL_API_COMPAT 0x10101000L /* FIXME: Use OpenSSL 1.1+ API. */ +# endif +# include +# endif + +# ifdef __cplusplus +extern "C" { +# endif + +# define SHA1_DIGEST_SIZE 20 + +# if HAVE_OPENSSL_SHA1 +# define GL_OPENSSL_NAME 1 +# include "gl_openssl.h" +# else +/* 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); + +# endif + +/* Compute SHA1 message digest for bytes read from STREAM. + STREAM is an open file stream. Regular files are handled more efficiently. + The contents of STREAM from its current position to its end will be read. + The case that the last operation on STREAM was an 'ungetc' is not supported. + The resulting message digest number will be written into the 20 bytes + beginning at RESBLOCK. */ +extern int sha1_stream (FILE *stream, void *resblock); + + +# ifdef __cplusplus +} +# endif + +#endif + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/lib/hmac/sha256.c b/lib/hmac/sha256.c new file mode 100644 index 00000000000..4a6fe5424f1 --- /dev/null +++ b/lib/hmac/sha256.c @@ -0,0 +1,430 @@ +/* sha256.c - Functions to compute SHA256 and SHA224 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 . */ + +/* Written by David Madore, considerably copypasting from + Scott G. Miller's sha1.c +*/ + +/* Specification. */ +#if HAVE_OPENSSL_SHA256 +# define GL_OPENSSL_INLINE _GL_EXTERN_INLINE +#endif +#include "sha256.h" + +#include +#include + +#ifdef WORDS_BIGENDIAN +# define SWAP(n) (n) +#else +#include "byteswap.h" +# define SWAP(n) swap_uint32 (n) +#endif + +#if ! HAVE_OPENSSL_SHA256 + +/* 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; +} + +void +sha224_init_ctx (struct sha256_ctx *ctx) +{ + ctx->state[0] = 0xc1059ed8UL; + ctx->state[1] = 0x367cd507UL; + ctx->state[2] = 0x3070dd17UL; + ctx->state[3] = 0xf70e5939UL; + ctx->state[4] = 0xffc00b31UL; + ctx->state[5] = 0x68581511UL; + ctx->state[6] = 0x64f98fa7UL; + ctx->state[7] = 0xbefa4fa4UL; + + 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; +} + +void * +sha224_read_ctx (const struct sha256_ctx *ctx, void *resbuf) +{ + int i; + char *r = resbuf; + + for (i = 0; i < 7; 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); +} + +void * +sha224_finish_ctx (struct sha256_ctx *ctx, void *resbuf) +{ + sha256_conclude_ctx (ctx); + return sha224_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 * +sha224_buffer (const char *buffer, size_t len, void *resblock) +{ + struct sha256_ctx ctx; + + /* Initialize the computation context. */ + sha224_init_ctx (&ctx); + + /* Process whole buffer but last len % 64 bytes. */ + sha256_process_bytes (buffer, len, &ctx); + + /* Put result in desired memory area. */ + return sha224_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); + 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++; + } + + R( a, b, c, d, e, f, g, h, K( 0), x[ 0] ); + R( h, a, b, c, d, e, f, g, K( 1), x[ 1] ); + R( g, h, a, b, c, d, e, f, K( 2), x[ 2] ); + R( f, g, h, a, b, c, d, e, K( 3), x[ 3] ); + R( e, f, g, h, a, b, c, d, K( 4), x[ 4] ); + R( d, e, f, g, h, a, b, c, K( 5), x[ 5] ); + R( c, d, e, f, g, h, a, b, K( 6), x[ 6] ); + R( b, c, d, e, f, g, h, a, K( 7), x[ 7] ); + R( a, b, c, d, e, f, g, h, K( 8), x[ 8] ); + R( h, a, b, c, d, e, f, g, K( 9), x[ 9] ); + R( g, h, a, b, c, d, e, f, K(10), x[10] ); + R( f, g, h, a, b, c, d, e, K(11), x[11] ); + R( e, f, g, h, a, b, c, d, K(12), x[12] ); + R( d, e, f, g, h, a, b, c, K(13), x[13] ); + R( c, d, e, f, g, h, a, b, K(14), x[14] ); + R( b, c, d, e, f, g, h, a, K(15), x[15] ); + R( a, b, c, d, e, f, g, h, K(16), M(16) ); + R( h, a, b, c, d, e, f, g, K(17), M(17) ); + R( g, h, a, b, c, d, e, f, K(18), M(18) ); + R( f, g, h, a, b, c, d, e, K(19), M(19) ); + R( e, f, g, h, a, b, c, d, K(20), M(20) ); + R( d, e, f, g, h, a, b, c, K(21), M(21) ); + R( c, d, e, f, g, h, a, b, K(22), M(22) ); + R( b, c, d, e, f, g, h, a, K(23), M(23) ); + R( a, b, c, d, e, f, g, h, K(24), M(24) ); + R( h, a, b, c, d, e, f, g, K(25), M(25) ); + R( g, h, a, b, c, d, e, f, K(26), M(26) ); + R( f, g, h, a, b, c, d, e, K(27), M(27) ); + R( e, f, g, h, a, b, c, d, K(28), M(28) ); + R( d, e, f, g, h, a, b, c, K(29), M(29) ); + R( c, d, e, f, g, h, a, b, K(30), M(30) ); + R( b, c, d, e, f, g, h, a, K(31), M(31) ); + R( a, b, c, d, e, f, g, h, K(32), M(32) ); + R( h, a, b, c, d, e, f, g, K(33), M(33) ); + R( g, h, a, b, c, d, e, f, K(34), M(34) ); + R( f, g, h, a, b, c, d, e, K(35), M(35) ); + R( e, f, g, h, a, b, c, d, K(36), M(36) ); + R( d, e, f, g, h, a, b, c, K(37), M(37) ); + R( c, d, e, f, g, h, a, b, K(38), M(38) ); + R( b, c, d, e, f, g, h, a, K(39), M(39) ); + R( a, b, c, d, e, f, g, h, K(40), M(40) ); + R( h, a, b, c, d, e, f, g, K(41), M(41) ); + R( g, h, a, b, c, d, e, f, K(42), M(42) ); + R( f, g, h, a, b, c, d, e, K(43), M(43) ); + R( e, f, g, h, a, b, c, d, K(44), M(44) ); + R( d, e, f, g, h, a, b, c, K(45), M(45) ); + R( c, d, e, f, g, h, a, b, K(46), M(46) ); + R( b, c, d, e, f, g, h, a, K(47), M(47) ); + R( a, b, c, d, e, f, g, h, K(48), M(48) ); + R( h, a, b, c, d, e, f, g, K(49), M(49) ); + R( g, h, a, b, c, d, e, f, K(50), M(50) ); + R( f, g, h, a, b, c, d, e, K(51), M(51) ); + R( e, f, g, h, a, b, c, d, K(52), M(52) ); + R( d, e, f, g, h, a, b, c, K(53), M(53) ); + R( c, d, e, f, g, h, a, b, K(54), M(54) ); + R( b, c, d, e, f, g, h, a, K(55), M(55) ); + R( a, b, c, d, e, f, g, h, K(56), M(56) ); + R( h, a, b, c, d, e, f, g, K(57), M(57) ); + R( g, h, a, b, c, d, e, f, K(58), M(58) ); + R( f, g, h, a, b, c, d, e, K(59), M(59) ); + R( e, f, g, h, a, b, c, d, K(60), M(60) ); + R( d, e, f, g, h, a, b, c, K(61), M(61) ); + R( c, d, e, f, g, h, a, b, K(62), M(62) ); + R( b, c, d, e, f, g, h, a, K(63), M(63) ); + + 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; + } +} + +#endif + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/lib/hmac/sha256.h b/lib/hmac/sha256.h new file mode 100644 index 00000000000..533173a59e8 --- /dev/null +++ b/lib/hmac/sha256.h @@ -0,0 +1,121 @@ +/* Declarations of functions and data types used for SHA256 and SHA224 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 . */ + +#ifndef SHA256_H +# define SHA256_H 1 + +# include +# include + +# if HAVE_OPENSSL_SHA256 +# ifndef OPENSSL_API_COMPAT +# define OPENSSL_API_COMPAT 0x10101000L /* FIXME: Use OpenSSL 1.1+ API. */ +# endif +# include +# endif + +# ifdef __cplusplus +extern "C" { +# endif + +enum { SHA224_DIGEST_SIZE = 224 / 8 }; +enum { SHA256_DIGEST_SIZE = 256 / 8 }; + +# if HAVE_OPENSSL_SHA256 +# define GL_OPENSSL_NAME 224 +# include "gl_openssl.h" +# define GL_OPENSSL_NAME 256 +# include "gl_openssl.h" +# else +/* 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); +extern void sha224_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); +extern void *sha224_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); +extern void *sha224_read_ctx (const struct sha256_ctx *ctx, + void *restrict resbuf); + + +/* Compute SHA256 (SHA224) 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); +extern void *sha224_buffer (const char *buffer, size_t len, + void *restrict resblock); + +# endif + +/* Compute SHA256 (SHA224) message digest for bytes read from STREAM. + STREAM is an open file stream. Regular files are handled more efficiently. + The contents of STREAM from its current position to its end will be read. + The case that the last operation on STREAM was an 'ungetc' is not supported. + The resulting message digest number will be written into the 32 (28) bytes + beginning at RESBLOCK. */ +extern int sha256_stream (FILE *stream, void *resblock); +extern int sha224_stream (FILE *stream, void *resblock); + + +# ifdef __cplusplus +} +# endif + +#endif + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/lib/hmac/sha512.c b/lib/hmac/sha512.c new file mode 100644 index 00000000000..a22f3954546 --- /dev/null +++ b/lib/hmac/sha512.c @@ -0,0 +1,475 @@ +/* sha512.c - Functions to compute SHA512 and SHA384 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 . */ + +/* Written by David Madore, considerably copypasting from + Scott G. Miller's sha1.c +*/ + +/* Specification. */ +#if HAVE_OPENSSL_SHA512 +# define GL_OPENSSL_INLINE _GL_EXTERN_INLINE +#endif +#include "sha512.h" + +#include +#include + +#ifdef WORDS_BIGENDIAN +# define SWAP(n) (n) +#else +#include "byteswap.h" +# define SWAP(n) swap_uint64 (n) +#endif + +#if ! HAVE_OPENSSL_SHA512 + +/* 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; +} + +void +sha384_init_ctx (struct sha512_ctx *ctx) +{ + ctx->state[0] = u64hilo (0xcbbb9d5d, 0xc1059ed8); + ctx->state[1] = u64hilo (0x629a292a, 0x367cd507); + ctx->state[2] = u64hilo (0x9159015a, 0x3070dd17); + ctx->state[3] = u64hilo (0x152fecd8, 0xf70e5939); + ctx->state[4] = u64hilo (0x67332667, 0xffc00b31); + ctx->state[5] = u64hilo (0x8eb44a87, 0x68581511); + ctx->state[6] = u64hilo (0xdb0c2e0d, 0x64f98fa7); + ctx->state[7] = u64hilo (0x47b5481d, 0xbefa4fa4); + + 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; +} + +void * +sha384_read_ctx (const struct sha512_ctx *ctx, void *resbuf) +{ + int i; + char *r = resbuf; + + for (i = 0; i < 6; 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); +} + +void * +sha384_finish_ctx (struct sha512_ctx *ctx, void *resbuf) +{ + sha512_conclude_ctx (ctx); + return sha384_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 * +sha384_buffer (const char *buffer, size_t len, void *resblock) +{ + struct sha512_ctx ctx; + + /* Initialize the computation context. */ + sha384_init_ctx (&ctx); + + /* Process whole buffer but last len % 128 bytes. */ + sha512_process_bytes (buffer, len, &ctx); + + /* Put result in desired memory area. */ + return sha384_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); + 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++; + } + + R( a, b, c, d, e, f, g, h, K( 0), x[ 0] ); + R( h, a, b, c, d, e, f, g, K( 1), x[ 1] ); + R( g, h, a, b, c, d, e, f, K( 2), x[ 2] ); + R( f, g, h, a, b, c, d, e, K( 3), x[ 3] ); + R( e, f, g, h, a, b, c, d, K( 4), x[ 4] ); + R( d, e, f, g, h, a, b, c, K( 5), x[ 5] ); + R( c, d, e, f, g, h, a, b, K( 6), x[ 6] ); + R( b, c, d, e, f, g, h, a, K( 7), x[ 7] ); + R( a, b, c, d, e, f, g, h, K( 8), x[ 8] ); + R( h, a, b, c, d, e, f, g, K( 9), x[ 9] ); + R( g, h, a, b, c, d, e, f, K(10), x[10] ); + R( f, g, h, a, b, c, d, e, K(11), x[11] ); + R( e, f, g, h, a, b, c, d, K(12), x[12] ); + R( d, e, f, g, h, a, b, c, K(13), x[13] ); + R( c, d, e, f, g, h, a, b, K(14), x[14] ); + R( b, c, d, e, f, g, h, a, K(15), x[15] ); + R( a, b, c, d, e, f, g, h, K(16), M(16) ); + R( h, a, b, c, d, e, f, g, K(17), M(17) ); + R( g, h, a, b, c, d, e, f, K(18), M(18) ); + R( f, g, h, a, b, c, d, e, K(19), M(19) ); + R( e, f, g, h, a, b, c, d, K(20), M(20) ); + R( d, e, f, g, h, a, b, c, K(21), M(21) ); + R( c, d, e, f, g, h, a, b, K(22), M(22) ); + R( b, c, d, e, f, g, h, a, K(23), M(23) ); + R( a, b, c, d, e, f, g, h, K(24), M(24) ); + R( h, a, b, c, d, e, f, g, K(25), M(25) ); + R( g, h, a, b, c, d, e, f, K(26), M(26) ); + R( f, g, h, a, b, c, d, e, K(27), M(27) ); + R( e, f, g, h, a, b, c, d, K(28), M(28) ); + R( d, e, f, g, h, a, b, c, K(29), M(29) ); + R( c, d, e, f, g, h, a, b, K(30), M(30) ); + R( b, c, d, e, f, g, h, a, K(31), M(31) ); + R( a, b, c, d, e, f, g, h, K(32), M(32) ); + R( h, a, b, c, d, e, f, g, K(33), M(33) ); + R( g, h, a, b, c, d, e, f, K(34), M(34) ); + R( f, g, h, a, b, c, d, e, K(35), M(35) ); + R( e, f, g, h, a, b, c, d, K(36), M(36) ); + R( d, e, f, g, h, a, b, c, K(37), M(37) ); + R( c, d, e, f, g, h, a, b, K(38), M(38) ); + R( b, c, d, e, f, g, h, a, K(39), M(39) ); + R( a, b, c, d, e, f, g, h, K(40), M(40) ); + R( h, a, b, c, d, e, f, g, K(41), M(41) ); + R( g, h, a, b, c, d, e, f, K(42), M(42) ); + R( f, g, h, a, b, c, d, e, K(43), M(43) ); + R( e, f, g, h, a, b, c, d, K(44), M(44) ); + R( d, e, f, g, h, a, b, c, K(45), M(45) ); + R( c, d, e, f, g, h, a, b, K(46), M(46) ); + R( b, c, d, e, f, g, h, a, K(47), M(47) ); + R( a, b, c, d, e, f, g, h, K(48), M(48) ); + R( h, a, b, c, d, e, f, g, K(49), M(49) ); + R( g, h, a, b, c, d, e, f, K(50), M(50) ); + R( f, g, h, a, b, c, d, e, K(51), M(51) ); + R( e, f, g, h, a, b, c, d, K(52), M(52) ); + R( d, e, f, g, h, a, b, c, K(53), M(53) ); + R( c, d, e, f, g, h, a, b, K(54), M(54) ); + R( b, c, d, e, f, g, h, a, K(55), M(55) ); + R( a, b, c, d, e, f, g, h, K(56), M(56) ); + R( h, a, b, c, d, e, f, g, K(57), M(57) ); + R( g, h, a, b, c, d, e, f, K(58), M(58) ); + R( f, g, h, a, b, c, d, e, K(59), M(59) ); + R( e, f, g, h, a, b, c, d, K(60), M(60) ); + R( d, e, f, g, h, a, b, c, K(61), M(61) ); + R( c, d, e, f, g, h, a, b, K(62), M(62) ); + R( b, c, d, e, f, g, h, a, K(63), M(63) ); + R( a, b, c, d, e, f, g, h, K(64), M(64) ); + R( h, a, b, c, d, e, f, g, K(65), M(65) ); + R( g, h, a, b, c, d, e, f, K(66), M(66) ); + R( f, g, h, a, b, c, d, e, K(67), M(67) ); + R( e, f, g, h, a, b, c, d, K(68), M(68) ); + R( d, e, f, g, h, a, b, c, K(69), M(69) ); + R( c, d, e, f, g, h, a, b, K(70), M(70) ); + R( b, c, d, e, f, g, h, a, K(71), M(71) ); + R( a, b, c, d, e, f, g, h, K(72), M(72) ); + R( h, a, b, c, d, e, f, g, K(73), M(73) ); + R( g, h, a, b, c, d, e, f, K(74), M(74) ); + R( f, g, h, a, b, c, d, e, K(75), M(75) ); + R( e, f, g, h, a, b, c, d, K(76), M(76) ); + R( d, e, f, g, h, a, b, c, K(77), M(77) ); + R( c, d, e, f, g, h, a, b, K(78), M(78) ); + R( b, c, d, e, f, g, h, a, K(79), M(79) ); + + 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); + } +} + +#endif + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/lib/hmac/sha512.h b/lib/hmac/sha512.h new file mode 100644 index 00000000000..1eb18702278 --- /dev/null +++ b/lib/hmac/sha512.h @@ -0,0 +1,124 @@ +/* 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 . */ + +#ifndef SHA512_H +# define SHA512_H 1 + +# include +# include "u64.h" + +# if HAVE_OPENSSL_SHA512 +# ifndef OPENSSL_API_COMPAT +# define OPENSSL_API_COMPAT 0x10101000L /* FIXME: Use OpenSSL 1.1+ API. */ +# endif +# include +# endif + +# ifdef __cplusplus +extern "C" { +# endif + +enum { SHA384_DIGEST_SIZE = 384 / 8 }; +enum { SHA512_DIGEST_SIZE = 512 / 8 }; + +# if HAVE_OPENSSL_SHA512 +# define GL_OPENSSL_NAME 384 +# include "gl_openssl.h" +# define GL_OPENSSL_NAME 512 +# include "gl_openssl.h" +# else +/* 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); +extern void sha384_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); +extern void *sha384_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); +extern void *sha384_read_ctx (const struct sha512_ctx *ctx, + void *restrict resbuf); + + +/* Compute SHA512 (SHA384) 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); +extern void *sha384_buffer (const char *buffer, size_t len, + void *restrict resblock); + +# endif + +/* Compute SHA512 (SHA384) message digest for bytes read from STREAM. + STREAM is an open file stream. Regular files are handled more efficiently. + The contents of STREAM from its current position to its end will be read. + The case that the last operation on STREAM was an 'ungetc' is not supported. + The resulting message digest number will be written into the 64 (48) bytes + beginning at RESBLOCK. */ +extern int sha512_stream (FILE *stream, void *resblock); +extern int sha384_stream (FILE *stream, void *resblock); + + +# ifdef __cplusplus +} +# endif + +#endif + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/lib/hmac/u64.c b/lib/hmac/u64.c new file mode 100644 index 00000000000..de6257ba084 --- /dev/null +++ b/lib/hmac/u64.c @@ -0,0 +1,20 @@ +/* uint64_t-like operations that work even on hosts lacking uint64_t + + Copyright (C) 2012-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 . */ + +#define _GL_U64_INLINE _GL_EXTERN_INLINE +#include "u64.h" +typedef int dummy; diff --git a/lib/hmac/u64.h b/lib/hmac/u64.h new file mode 100644 index 00000000000..f57ea6550b3 --- /dev/null +++ b/lib/hmac/u64.h @@ -0,0 +1,42 @@ +/* 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 . */ + +/* Written by Paul Eggert. */ + +#include + +#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)) diff --git a/lib/list/list.c b/lib/list/list.c new file mode 100644 index 00000000000..08c9d9930fa --- /dev/null +++ b/lib/list/list.c @@ -0,0 +1,72 @@ +#include "list.h" + +ListNode *list_init_head(void* data) { + ListNode *new = (ListNode *) malloc(sizeof(ListNode)); + new->data = data; + new->next = NULL; + return new; +} + +ListNode *list_add(ListNode *head, void* data) { + ListNode *new = (ListNode *) malloc(sizeof(ListNode)); + 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, void* data) { + ListNode *it; + + 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 == 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; +} + +void list_free(ListNode *head) { + ListNode *it = head, *tmp; + + while (it != NULL) { + tmp = it; + it = it->next; + free(tmp); + } +} diff --git a/lib/list/list.h b/lib/list/list.h new file mode 100644 index 00000000000..4d0bbd6f81b --- /dev/null +++ b/lib/list/list.h @@ -0,0 +1,19 @@ +#ifndef _TOTP_LIST_H_ +#define _TOTP_LIST_H_ + +#include +#include + +typedef struct ListNode { + void* data; + struct ListNode *next; +} ListNode; + +ListNode *list_init_head(void* data); +ListNode *list_add(ListNode *head, void* data); /* adds element with specified data to the end of the list and returns new head node. */ +ListNode *list_find(ListNode *head, void* data); /* returns pointer of element with specified data in list. */ +ListNode *list_element_at(ListNode *head, uint16_t index); /* returns pointer of element with specified index in list. */ +ListNode *list_remove(ListNode *head, ListNode *ep); /* removes element from the list and returns new head node. */ +void list_free(ListNode *head); /* deletes all elements of the list. */ + +#endif diff --git a/lib/timezone_utils/timezone_utils.c b/lib/timezone_utils/timezone_utils.c new file mode 100644 index 00000000000..7638da89fb5 --- /dev/null +++ b/lib/timezone_utils/timezone_utils.c @@ -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; +} diff --git a/lib/timezone_utils/timezone_utils.h b/lib/timezone_utils/timezone_utils.h new file mode 100644 index 00000000000..9d15d7a0828 --- /dev/null +++ b/lib/timezone_utils/timezone_utils.h @@ -0,0 +1,9 @@ +#ifndef _TIMEZONE_UTILS_H_ +#define _TIMEZONE_UTILS_H_ + +#include + +int32_t timezone_offset_from_hours(float hours); +uint64_t timezone_offset_apply(uint64_t time, int32_t offset); + +#endif diff --git a/lib/totp/totp.c b/lib/totp/totp.c new file mode 100644 index 00000000000..9a1c7f23405 --- /dev/null +++ b/lib/totp/totp.c @@ -0,0 +1,123 @@ +#include "totp.h" + +#include +#include +#include +#include +#include +#include "../hmac/hmac-sha1.h" +#include "../hmac/hmac-sha256.h" +#include "../hmac/hmac-sha512.h" +#include "../timezone_utils/timezone_utils.h" + +#define UINT64_GET_BYTE(integer, index) ((integer >> (8 * index)) & 0xFF) + +/* + 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. + + for_time is a time in seconds to get the current timeblocks + + Returns + timeblock given for_time, using data->interval + error, 0 +*/ +uint64_t totp_timecode(uint8_t interval, uint64_t for_time) +{ + return for_time/interval; +} + +/* + Converts an integer into an 8 byte array. + + out_bytes is the null-terminated output string already allocated +*/ +void otp_num_to_bytes(uint64_t integer, uint8_t* out_bytes) +{ + out_bytes[7] = UINT64_GET_BYTE(integer, 0); + out_bytes[6] = UINT64_GET_BYTE(integer, 1); + out_bytes[5] = UINT64_GET_BYTE(integer, 2); + out_bytes[4] = UINT64_GET_BYTE(integer, 3); + out_bytes[3] = UINT64_GET_BYTE(integer, 4); + out_bytes[2] = UINT64_GET_BYTE(integer, 5); + out_bytes[1] = UINT64_GET_BYTE(integer, 6); + out_bytes[0] = UINT64_GET_BYTE(integer, 7); +} + +/* + Generates an OTP (One Time Password). + + input is a number used to generate the OTP + out_str is the null-terminated output string already allocated + + Returns + OTP code if otp code was successfully generated + 0 otherwise +*/ +uint32_t otp_generate(TOTP_ALGO algo, uint8_t digits, const uint8_t* plain_secret, uint8_t plain_secret_length, uint64_t input) +{ + uint8_t* bytes = malloc(8); + memset(bytes, 0, 8); + uint8_t* hmac = malloc(64); + memset(hmac, 0, 64); + + otp_num_to_bytes(input, bytes); + + int hmac_len = (*(algo))(plain_secret, plain_secret_length, bytes, 8, hmac); + if (hmac_len == 0) { + free(hmac); + free(bytes); + 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); + + free(hmac); + free(bytes); + return i_code; +} + +/* + Generates a OTP key using the totp algorithm. + + for_time is the time the generated key will be created for + offset is a timeblock adjustment for the generated key + out_str is the null-terminated output string already allocated + + Returns + TOTP code if otp code was successfully generated + 0 otherwise +*/ +uint32_t totp_at(TOTP_ALGO algo, uint8_t digits, const uint8_t* plain_secret, uint8_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, uint8_t key_length, const uint8_t* input, uint8_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, uint8_t key_length, const uint8_t* input, uint8_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, uint8_t key_length, const uint8_t* input, uint8_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); diff --git a/lib/totp/totp.h b/lib/totp/totp.h new file mode 100644 index 00000000000..d03abfb504b --- /dev/null +++ b/lib/totp/totp.h @@ -0,0 +1,44 @@ +#ifndef _TOTP_H_ +#define _TOTP_H_ + +#include +#include + +#define OTP_ERROR (0) + +/* + Must compute HMAC using passed arguments, + output as char array through output. + + key is secret key. + input is input number. + 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, uint8_t key_length, const uint8_t* input, uint8_t input_length, uint8_t* output); + +/* + Computes HMAC using SHA1 +*/ +extern const TOTP_ALGO TOTP_ALGO_SHA1; + +/* + Computes HMAC using SHA256 +*/ +extern const TOTP_ALGO TOTP_ALGO_SHA256; + +/* + Computes HMAC using SHA512 +*/ +extern const TOTP_ALGO TOTP_ALGO_SHA512; + +/* + Computes TOTP token + Returns: + TOTP token on success + 0 otherwise +*/ +uint32_t totp_at(TOTP_ALGO algo, uint8_t digits, const uint8_t* plain_secret, uint8_t plain_secret_length, uint64_t for_time, float timezone, uint8_t interval); + +#endif diff --git a/lib/ui/constants.h b/lib/ui/constants.h new file mode 100644 index 00000000000..0946a763d6b --- /dev/null +++ b/lib/ui/constants.h @@ -0,0 +1,9 @@ +#ifndef _TOTP_UI_CONSTANTS_H_ +#define _TOTP_UI_CONSTANTS_H_ + +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 +#define SCREEN_WIDTH_CENTER (SCREEN_WIDTH >> 1) +#define SCREEN_HEIGHT_CENTER (SCREEN_HEIGHT >> 1) + +#endif diff --git a/lib/ui/icons.h b/lib/ui/icons.h new file mode 100644 index 00000000000..ea4b6812e29 --- /dev/null +++ b/lib/ui/icons.h @@ -0,0 +1,14 @@ +#ifndef _TOTP_ICONS_H_ +#define _TOTP_ICONS_H_ + +#include + +#define ICON_ARROW_LEFT_8x9_WIDTH 8 +#define ICON_ARROW_LEFT_8x9_HEIGHT 9 +static const uint8_t ICON_ARROW_LEFT_8x9[] = { 0x80,0xe0,0xf8,0xfe,0xff,0xfe,0xf8,0xe0,0x80 }; + +#define ICON_ARROW_RIGHT_8x9_WIDTH 8 +#define ICON_ARROW_RIGHT_8x9_HEIGHT 9 +static const uint8_t ICON_ARROW_RIGHT_8x9[] = { 0x01,0x07,0x1f,0x7f,0xff,0x7f,0x1f,0x07,0x01 }; + +#endif diff --git a/lib/ui/ui_controls.c b/lib/ui/ui_controls.c new file mode 100644 index 00000000000..b73f43fc8fc --- /dev/null +++ b/lib/ui/ui_controls.c @@ -0,0 +1,56 @@ +#include "ui_controls.h" +#include "constants.h" +#include "icons.h" + +#define TEXT_BOX_HEIGHT 13 +#define TEXT_BOX_MARGIN 4 + +void ui_control_text_box_render(Canvas* const canvas, int8_t y, 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, int8_t y, 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, SCREEN_WIDTH_CENTER, TEXT_BOX_MARGIN + 3 + y, AlignCenter, AlignTop, text); + canvas_draw_xbm(canvas, TEXT_BOX_MARGIN + 2, TEXT_BOX_MARGIN + 2 + y, ICON_ARROW_LEFT_8x9_WIDTH, ICON_ARROW_LEFT_8x9_HEIGHT, &ICON_ARROW_LEFT_8x9[0]); + canvas_draw_xbm(canvas, SCREEN_WIDTH - TEXT_BOX_MARGIN - 10, TEXT_BOX_MARGIN + 2 + y, ICON_ARROW_RIGHT_8x9_WIDTH, ICON_ARROW_RIGHT_8x9_HEIGHT, &ICON_ARROW_RIGHT_8x9[0]); +} + +void ui_control_button_render(Canvas* const canvas, uint8_t x, int8_t y, uint8_t width, uint8_t height, 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); + } +} diff --git a/lib/ui/ui_controls.h b/lib/ui/ui_controls.h new file mode 100644 index 00000000000..df1fb27bfdc --- /dev/null +++ b/lib/ui/ui_controls.h @@ -0,0 +1,11 @@ +#ifndef _TOTP_UI_CONTROLS_H_ +#define _TOTP_UI_CONTROLS_H_ + +#include +#include + +void ui_control_text_box_render(Canvas* const canvas, int8_t y, char* text, bool is_selected); +void ui_control_button_render(Canvas* const canvas, uint8_t x, int8_t y, uint8_t width, uint8_t height, char* text, bool is_selected); +void ui_control_select_render(Canvas* const canvas, int8_t y, char* text, bool is_selected); + +#endif diff --git a/scenes/add_new_token/totp_input_text.c b/scenes/add_new_token/totp_input_text.c new file mode 100644 index 00000000000..2cf2c9c1473 --- /dev/null +++ b/scenes/add_new_token/totp_input_text.c @@ -0,0 +1,75 @@ +#include "totp_input_text.h" +#include +#include "../../types/common.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)); + result->user_input_length = strlen(text_input_state->text_input_buffer); + result->user_input = malloc(result->user_input_length + 1); + result->callback_data = text_input_state->callback_data; + strcpy(result->user_input, text_input_state->text_input_buffer); + text_input_state->callback(result); + } +} + +InputTextSceneState* totp_input_text_activate(InputTextSceneContext* context) { + InputTextSceneState* text_input_state = malloc(sizeof(InputTextSceneState)); + 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); +} diff --git a/scenes/add_new_token/totp_input_text.h b/scenes/add_new_token/totp_input_text.h new file mode 100644 index 00000000000..945835fcea0 --- /dev/null +++ b/scenes/add_new_token/totp_input_text.h @@ -0,0 +1,41 @@ +#ifndef _TOTP_INPUT_TEXT_H_ +#define _TOTP_INPUT_TEXT_H_ + +#include +#include +#include +#include +#include +#include "../../types/plugin_state.h" +#include "../../types/plugin_event.h" + +typedef struct { + char* user_input; + uint8_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); + +#endif diff --git a/scenes/add_new_token/totp_scene_add_new_token.c b/scenes/add_new_token/totp_scene_add_new_token.c new file mode 100644 index 00000000000..9645c6c191e --- /dev/null +++ b/scenes/add_new_token/totp_scene_add_new_token.c @@ -0,0 +1,266 @@ +#include "totp_scene_add_new_token.h" +#include "../../types/common.h" +#include "../../lib/ui/constants.h" +#include "../scene_director.h" +#include "totp_input_text.h" +#include "../../types/token_info.h" +#include "../../lib/list/list.h" +#include "../../lib/base32/base32.h" +#include "../../lib/config/config.h" +#include "../../lib/ui/ui_controls.h" +#include "../generate_token/totp_scene_generate_token.h" + +#define TOKEN_ALGO_LIST_LENGTH 3 +char* TOKEN_ALGO_LIST[] = { "SHA1", "SHA256", "SHA512" }; +#define TOKEN_DIGITS_LIST_LENGTH 2 +char* TOKEN_DIGITS_LIST[] = { "6 digits", "8 digits" }; + +typedef enum { + TokenNameTextBox, + TokenSecretTextBox, + TokenAlgoSelect, + TokenLengthSelect, + ConfirmButton, +} Control; + +typedef struct { + char* token_name; + uint8_t token_name_length; + char* token_secret; + uint8_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; + int current_token_index; + int32_t screen_y_offset; + TokenHashAlgo algo; + TokenDigitsCount digits_count; +} SceneState; + +void totp_scene_add_new_token_init(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)); + 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)); + 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)); + 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) { + scene_state->current_token_index = -1; + } else { + 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, 44 - scene_state->screen_y_offset, TOKEN_ALGO_LIST[scene_state->algo], scene_state->selected_control == TokenAlgoSelect); + ui_control_select_render(canvas, 63 - scene_state->screen_y_offset, TOKEN_DIGITS_LIST[scene_state->digits_count], 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) { + 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; + } else if(event->input.type == InputTypePress) { + switch(event->input.key) { + case InputKeyUp: + if (scene_state->selected_control > TokenNameTextBox) { + scene_state->selected_control--; + update_screen_y_offset(scene_state); + } + break; + case InputKeyDown: + if (scene_state->selected_control < ConfirmButton) { + scene_state->selected_control++; + update_screen_y_offset(scene_state); + } + break; + case InputKeyRight: + if (scene_state->selected_control == TokenAlgoSelect) { + if (scene_state->algo < SHA512) { + scene_state->algo++; + } else { + scene_state->algo = SHA1; + } + } + else if (scene_state->selected_control == TokenLengthSelect) { + if (scene_state->digits_count < TOTP_8_DIGITS) { + scene_state->digits_count++; + } else { + scene_state->digits_count = TOTP_6_DIGITS; + } + } + break; + case InputKeyLeft: + if (scene_state->selected_control == TokenAlgoSelect) { + if (scene_state->algo > SHA1) { + scene_state->algo--; + } else { + scene_state->algo = SHA512; + } + } + else if (scene_state->selected_control == TokenLengthSelect) { + if (scene_state->digits_count > TOTP_6_DIGITS) { + scene_state->digits_count--; + } else { + scene_state->digits_count = TOTP_8_DIGITS; + } + } + 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(); + tokenInfo->name = malloc(scene_state->token_name_length + 1); + strcpy(tokenInfo->name, scene_state->token_name); + + token_info_set_secret(tokenInfo, scene_state->token_secret, scene_state->token_secret_length, &plugin_state->iv[0]); + + tokenInfo->algo = scene_state->algo; + tokenInfo->digits = scene_state->digits_count; + + if (plugin_state->tokens_list == NULL) { + plugin_state->tokens_list = list_init_head(tokenInfo); + } else { + list_add(plugin_state->tokens_list, tokenInfo); + } + plugin_state->tokens_count++; + + Storage* cfg_storage = totp_open_storage(); + FlipperFormat* cfg_file = totp_open_config_file(cfg_storage); + + flipper_format_seek_to_end(cfg_file); + totp_config_file_save_new_token(cfg_file, tokenInfo); + + totp_close_config_file(cfg_file); + totp_close_storage(); + + GenerateTokenSceneContext generate_scene_context = { .current_token_index = plugin_state->tokens_count - 1 }; + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, &generate_scene_context); + break; + } + } + break; + case InputKeyBack: + if (scene_state->current_token_index >= 0) { + GenerateTokenSceneContext generate_scene_context = { .current_token_index = scene_state->current_token_index }; + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, &generate_scene_context); + } else { + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } + 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(PluginState* plugin_state) { + UNUSED(plugin_state); +} diff --git a/scenes/add_new_token/totp_scene_add_new_token.h b/scenes/add_new_token/totp_scene_add_new_token.h new file mode 100644 index 00000000000..653e17319f5 --- /dev/null +++ b/scenes/add_new_token/totp_scene_add_new_token.h @@ -0,0 +1,21 @@ +#ifndef _TOTP_SCENE_ADD_NEW_TOKEN_H_ +#define _TOTP_SCENE_ADD_NEW_TOKEN_H_ + +#include +#include +#include +#include "../../types/plugin_state.h" +#include "../../types/plugin_event.h" + +typedef struct { + uint8_t current_token_index; +} TokenAddEditSceneContext; + +void totp_scene_add_new_token_init(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(PluginState* plugin_state); + +#endif diff --git a/scenes/authenticate/totp_scene_authenticate.c b/scenes/authenticate/totp_scene_authenticate.c new file mode 100644 index 00000000000..068d118af56 --- /dev/null +++ b/scenes/authenticate/totp_scene_authenticate.c @@ -0,0 +1,169 @@ +#include "totp_scene_authenticate.h" +#include +#include "../../types/common.h" +#include "../../lib/ui/icons.h" +#include "../../lib/ui/constants.h" +#include "../../lib/config/config.h" +#include "../scene_director.h" +#include "../totp_scenes_enum.h" + +#define MAX_CODE_LENGTH TOTP_IV_SIZE +#define CRYPTO_VERIFY_KEY "FFF_Crypto_pass" +#define CRYPTO_VERIFY_KEY_LENGTH 16 + +typedef struct { + uint8_t 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)); + scene_state->code_length = 0; + memset(&scene_state->code_input[0], 0, MAX_CODE_LENGTH); + plugin_state->current_scene_state = scene_state; +} + +void totp_scene_authenticate_render(Canvas* const canvas, PluginState* plugin_state) { + 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(PluginEvent* const event, PluginState* plugin_state) { + if(event->type == EventTypeKey) { + if (event->input.type == InputTypeLong && event->input.key == InputKeyBack) { + return false; + } else if(event->input.type == InputTypePress) { + SceneState* scene_state = (SceneState *)plugin_state->current_scene_state; + + const uint8_t ARROW_UP_CODE = 2; + const uint8_t ARROW_RIGHT_CODE = 8; + const uint8_t ARROW_DOWN_CODE = 11; + const uint8_t ARROW_LEFT_CODE = 5; + + switch(event->input.key) { + case InputKeyUp: + if (scene_state->code_length < MAX_CODE_LENGTH) { + scene_state->code_input[scene_state->code_length] = ARROW_UP_CODE; + scene_state->code_length++; + } + break; + case InputKeyDown: + if (scene_state->code_length < MAX_CODE_LENGTH) { + scene_state->code_input[scene_state->code_length] = ARROW_DOWN_CODE; + scene_state->code_length++; + } + break; + case InputKeyRight: + if (scene_state->code_length < MAX_CODE_LENGTH) { + scene_state->code_input[scene_state->code_length] = ARROW_RIGHT_CODE; + scene_state->code_length++; + } + break; + case InputKeyLeft: + if (scene_state->code_length < MAX_CODE_LENGTH) { + scene_state->code_input[scene_state->code_length] = ARROW_LEFT_CODE; + scene_state->code_length++; + } + break; + case InputKeyOk: + 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); + for (uint8_t i = 0; i < scene_state->code_length; i++) { + plugin_state->iv[i] = plugin_state->iv[i] ^ (uint8_t)(scene_state->code_input[i] * (i + 1)); + } + + 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); + plugin_state->crypto_verify_data_length = CRYPTO_VERIFY_KEY_LENGTH; + Storage* storage = totp_open_storage(); + FlipperFormat* config_file = totp_open_config_file(storage); + furi_hal_crypto_store_load_key(CRYPTO_KEY_SLOT, &plugin_state->iv[0]); + furi_hal_crypto_encrypt((uint8_t* )CRYPTO_VERIFY_KEY, plugin_state->crypto_verify_data, CRYPTO_VERIFY_KEY_LENGTH); + furi_hal_crypto_store_unload_key(CRYPTO_KEY_SLOT); + flipper_format_insert_or_update_hex(config_file, TOTP_CONFIG_KEY_BASE_IV, plugin_state->base_iv, TOTP_IV_SIZE); + flipper_format_insert_or_update_hex(config_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, plugin_state->crypto_verify_data, CRYPTO_VERIFY_KEY_LENGTH); + totp_close_config_file(config_file); + totp_close_storage(); + } + + uint8_t decrypted_key[CRYPTO_VERIFY_KEY_LENGTH]; + furi_hal_crypto_store_load_key(CRYPTO_KEY_SLOT, &plugin_state->iv[0]); + furi_hal_crypto_decrypt(plugin_state->crypto_verify_data, &decrypted_key[0], CRYPTO_VERIFY_KEY_LENGTH); + furi_hal_crypto_store_unload_key(CRYPTO_KEY_SLOT); + + 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; + } + + if (key_valid) { + 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, 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; + } + } + } + + 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(PluginState* plugin_state) { + UNUSED(plugin_state); +} diff --git a/scenes/authenticate/totp_scene_authenticate.h b/scenes/authenticate/totp_scene_authenticate.h new file mode 100644 index 00000000000..d301c0c34a9 --- /dev/null +++ b/scenes/authenticate/totp_scene_authenticate.h @@ -0,0 +1,17 @@ +#ifndef _TOTP_SCENE_AUTHENTICATE_H_ +#define _TOTP_SCENE_AUTHENTICATE_H_ + +#include +#include +#include +#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(PluginEvent* const event, PluginState* plugin_state); +void totp_scene_authenticate_deactivate(PluginState* plugin_state); +void totp_scene_authenticate_free(PluginState* plugin_state); + +#endif diff --git a/scenes/generate_token/totp_scene_generate_token.c b/scenes/generate_token/totp_scene_generate_token.c new file mode 100644 index 00000000000..acd3a13793e --- /dev/null +++ b/scenes/generate_token/totp_scene_generate_token.c @@ -0,0 +1,240 @@ +#include +#include +#include +#include "totp_scene_generate_token.h" +#include "../../types/token_info.h" +#include "../../types/common.h" +#include "../../lib/ui/icons.h" +#include "../../lib/ui/constants.h" +#include "../../lib/totp/totp.h" +#include "../../lib/config/config.h" +#include "../scene_director.h" +#include "../token_menu/totp_scene_token_menu.h" + +#define TOKEN_LIFETIME 30 +#define DIGIT_TO_CHAR(digit) ((digit) + '0') + +typedef struct { + uint8_t current_token_index; + char last_code[9]; + char* last_code_name; + bool need_token_update; + uint32_t last_token_gen_time; +} SceneState; + +static const NotificationSequence sequence_short_vibro_and_sound = { + &message_display_backlight_on, + &message_green_255, + &message_vibro_on, + &message_note_c5, + &message_delay_50, + &message_vibro_off, + &message_sound_off, + NULL, +}; + +static void i_token_to_str(uint32_t i_token_code, char* str, TokenDigitsCount len) { + if (len == TOTP_8_DIGITS) { + str[8] = '\0'; + } else if (len == TOTP_6_DIGITS) { + str[6] = '\0'; + } + + if (i_token_code == 0) { + if (len > TOTP_6_DIGITS) { + str[7] = '-'; + str[6] = '-'; + } + + str[5] = '-'; + str[4] = '-'; + str[3] = '-'; + str[2] = '-'; + str[1] = '-'; + str[0] = '-'; + } else { + if (len == TOTP_8_DIGITS) { + str[7] = DIGIT_TO_CHAR(i_token_code % 10); + str[6] = DIGIT_TO_CHAR((i_token_code = i_token_code / 10) % 10); + str[5] = DIGIT_TO_CHAR((i_token_code = i_token_code / 10) % 10); + } else if (len == TOTP_6_DIGITS) { + str[5] = DIGIT_TO_CHAR(i_token_code % 10); + } + + str[4] = DIGIT_TO_CHAR((i_token_code = i_token_code / 10) % 10); + str[3] = DIGIT_TO_CHAR((i_token_code = i_token_code / 10) % 10); + str[2] = DIGIT_TO_CHAR((i_token_code = i_token_code / 10) % 10); + str[1] = DIGIT_TO_CHAR((i_token_code = i_token_code / 10) % 10); + str[0] = DIGIT_TO_CHAR((i_token_code = i_token_code / 10) % 10); + } +} + +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; + } + + return NULL; +} + +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 = (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(PluginState* plugin_state) { + UNUSED(plugin_state); +} + +void totp_scene_generate_token_activate(PluginState* plugin_state, const GenerateTokenSceneContext* context) { + if (!plugin_state->token_list_loaded) { + totp_config_file_load_tokens(plugin_state); + } + SceneState* scene_state = malloc(sizeof(SceneState)); + if (context == NULL) { + 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); +} + +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; + + TokenInfo* tokenInfo = (TokenInfo*)(list_element_at(plugin_state->tokens_list, scene_state->current_token_index)->data); + + uint8_t* key = malloc(tokenInfo->token_length); + + furi_hal_crypto_store_load_key(CRYPTO_KEY_SLOT, &plugin_state->iv[0]); + furi_hal_crypto_decrypt(tokenInfo->token, key, tokenInfo->token_length); + furi_hal_crypto_store_unload_key(CRYPTO_KEY_SLOT); + + i_token_to_str(totp_at(get_totp_algo_impl(tokenInfo->algo), token_info_get_digits_count(tokenInfo), key, tokenInfo->token_length, curr_ts, plugin_state->timezone_offset, TOKEN_LIFETIME), scene_state->last_code, tokenInfo->digits); + memset(key, 0, tokenInfo->token_length); + free(key); + + if (is_new_token_time) { + notification_message(plugin_state->notification, &sequence_short_vibro_and_sound); + } + } + + 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_xbm(canvas, 0, SCREEN_HEIGHT_CENTER - 24, ICON_ARROW_LEFT_8x9_WIDTH, ICON_ARROW_LEFT_8x9_HEIGHT, &ICON_ARROW_LEFT_8x9[0]); + canvas_draw_xbm(canvas, SCREEN_WIDTH - 9, SCREEN_HEIGHT_CENTER - 24, ICON_ARROW_RIGHT_8x9_WIDTH, ICON_ARROW_RIGHT_8x9_HEIGHT, &ICON_ARROW_RIGHT_8x9[0]); + } +} + +bool totp_scene_generate_token_handle_event(PluginEvent* const event, PluginState* plugin_state) { + if(event->type == EventTypeKey) { + if (event->input.type == InputTypeLong && event->input.key == InputKeyBack) { + return false; + } else if(event->input.type == InputTypePress) { + SceneState* scene_state = (SceneState *)plugin_state->current_scene_state; + switch(event->input.key) { + case InputKeyUp: + break; + case InputKeyDown: + break; + case InputKeyRight: + if (scene_state->current_token_index < plugin_state->tokens_count - 1) { + scene_state->current_token_index++; + } else { + scene_state->current_token_index = 0; + } + update_totp_params(plugin_state); + break; + case InputKeyLeft: + if (scene_state->current_token_index > 0) { + scene_state->current_token_index--; + } else { + scene_state->current_token_index = plugin_state->tokens_count - 1; + } + update_totp_params(plugin_state); + break; + case InputKeyOk: + if (plugin_state->tokens_count == 0) { + totp_scene_director_activate_scene(plugin_state, TotpSceneAddNewToken, 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; + } + } + } + + 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; + + free(scene_state->last_code); + free(scene_state); + plugin_state->current_scene_state = NULL; +} + +void totp_scene_generate_token_free(PluginState* plugin_state) { + UNUSED(plugin_state); +} diff --git a/scenes/generate_token/totp_scene_generate_token.h b/scenes/generate_token/totp_scene_generate_token.h new file mode 100644 index 00000000000..40dc47a6965 --- /dev/null +++ b/scenes/generate_token/totp_scene_generate_token.h @@ -0,0 +1,21 @@ +#ifndef _TOTP_SCENE_GENERATE_TOKEN_H_ +#define _TOTP_SCENE_GENERATE_TOKEN_H_ + +#include +#include +#include +#include "../../types/plugin_state.h" +#include "../../types/plugin_event.h" + +typedef struct { + uint8_t current_token_index; +} GenerateTokenSceneContext; + +void totp_scene_generate_token_init(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(PluginEvent* const event, PluginState* plugin_state); +void totp_scene_generate_token_deactivate(PluginState* plugin_state); +void totp_scene_generate_token_free(PluginState* plugin_state); + +#endif diff --git a/scenes/scene_director.c b/scenes/scene_director.c new file mode 100644 index 00000000000..428df9080b0 --- /dev/null +++ b/scenes/scene_director.c @@ -0,0 +1,96 @@ +#include "../types/common.h" +#include "scene_director.h" +#include "authenticate/totp_scene_authenticate.h" +#include "generate_token/totp_scene_generate_token.h" +#include "add_new_token/totp_scene_add_new_token.h" +#include "token_menu/totp_scene_token_menu.h" + +void totp_scene_director_activate_scene(PluginState* const plugin_state, Scene scene, const void* context) { + plugin_state->changing_scene = true; + 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; + } + + plugin_state->current_scene = scene; + plugin_state->changing_scene = false; +} + +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; + } +} + +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); +} + +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; + } +} + +void totp_scene_director_dispose(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); +} + +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; + } + + return processing; +} diff --git a/scenes/scene_director.h b/scenes/scene_director.h new file mode 100644 index 00000000000..962b7e9a797 --- /dev/null +++ b/scenes/scene_director.h @@ -0,0 +1,16 @@ +#ifndef _SCENE_DIRECTOR_H_ +#define _SCENE_DIRECTOR_H_ + +#include +#include "../types/plugin_state.h" +#include "../types/plugin_event.h" +#include "totp_scenes_enum.h" + +void totp_scene_director_activate_scene(PluginState* const plugin_state, Scene scene, const void* context); +void totp_scene_director_deactivate_active_scene(PluginState* const plugin_state); +void totp_scene_director_init_scenes(PluginState* const plugin_state); +void totp_scene_director_render(Canvas* const canvas, PluginState* const plugin_state); +void totp_scene_director_dispose(PluginState* const plugin_state); +bool totp_scene_director_handle_event(PluginEvent* const event, PluginState* const plugin_state); + +#endif diff --git a/scenes/token_menu/totp_scene_token_menu.c b/scenes/token_menu/totp_scene_token_menu.c new file mode 100644 index 00000000000..122f8cc1778 --- /dev/null +++ b/scenes/token_menu/totp_scene_token_menu.c @@ -0,0 +1,113 @@ +#include "totp_scene_token_menu.h" +#include +#include +#include "../../lib/ui/ui_controls.h" +#include "../../lib/ui/constants.h" +#include "../scene_director.h" +#include "../../lib/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" + +typedef enum { + AddNewToken, + DeleteToken +} Control; + +typedef struct { + Control selected_control; + uint8_t current_token_index; +} SceneState; + +void totp_scene_token_menu_init(PluginState* plugin_state) { + UNUSED(plugin_state); +} + +void totp_scene_token_menu_activate(PluginState* plugin_state, const TokenMenuSceneContext* context) { + SceneState* scene_state = malloc(sizeof(SceneState)); + plugin_state->current_scene_state = scene_state; + scene_state->current_token_index = context->current_token_index; +} + +void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_state) { + SceneState* scene_state = (SceneState *)plugin_state->current_scene_state; + 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, "Delete token", scene_state->selected_control == DeleteToken); +} + +bool totp_scene_token_menu_handle_event(PluginEvent* const event, PluginState* plugin_state) { + if (event->type == EventTypeKey) { + SceneState* scene_state = (SceneState *)plugin_state->current_scene_state; + if(event->input.type == InputTypePress) { + switch(event->input.key) { + case InputKeyUp: + if (scene_state->selected_control > AddNewToken) { + scene_state->selected_control--; + } + break; + case InputKeyDown: + if (scene_state->selected_control < DeleteToken) { + scene_state->selected_control++; + } + break; + case InputKeyRight: + break; + case InputKeyLeft: + break; + case InputKeyOk: + switch (scene_state->selected_control) { + case AddNewToken: { + TokenAddEditSceneContext add_new_token_scene_context = { .current_token_index = scene_state->current_token_index }; + 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, message); + dialog_message_free(message); + if (dialog_result == DialogMessageButtonRight) { + uint8_t i = 0; + + ListNode* list_node = plugin_state->tokens_list; + while (i < scene_state->current_token_index && list_node->next != NULL) { + list_node = list_node->next; + i++; + } + + TokenInfo* tokenInfo = list_node->data; + token_info_free(tokenInfo); + plugin_state->tokens_list = list_remove(plugin_state->tokens_list, list_node); + plugin_state->tokens_count--; + + totp_full_save_config_file(plugin_state); + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } + break; + } + } + break; + case InputKeyBack: { + GenerateTokenSceneContext generate_scene_context = { .current_token_index = scene_state->current_token_index }; + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, &generate_scene_context); + 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(PluginState* plugin_state) { + UNUSED(plugin_state); +} diff --git a/scenes/token_menu/totp_scene_token_menu.h b/scenes/token_menu/totp_scene_token_menu.h new file mode 100644 index 00000000000..a73b5e8a3b9 --- /dev/null +++ b/scenes/token_menu/totp_scene_token_menu.h @@ -0,0 +1,21 @@ +#ifndef _TOTP_SCENE_TOKEN_MENU_H_ +#define _TOTP_SCENE_TOKEN_MENU_H_ + +#include +#include +#include +#include "../../types/plugin_state.h" +#include "../../types/plugin_event.h" + +typedef struct { + uint8_t current_token_index; +} TokenMenuSceneContext; + +void totp_scene_token_menu_init(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(PluginEvent* const event, PluginState* plugin_state); +void totp_scene_token_menu_deactivate(PluginState* plugin_state); +void totp_scene_token_menu_free(PluginState* plugin_state); + +#endif diff --git a/scenes/totp_scenes_enum.h b/scenes/totp_scenes_enum.h new file mode 100644 index 00000000000..3e45992e59e --- /dev/null +++ b/scenes/totp_scenes_enum.h @@ -0,0 +1,9 @@ +#ifndef _TOTP_SCENES_ENUM_H_ +#define _TOTP_SCENES_ENUM_H_ +typedef enum { + TotpSceneAuthentication, + TotpSceneGenerateToken, + TotpSceneAddNewToken, + TotpSceneTokenMenu +} Scene; +#endif diff --git a/totp_10px.png b/totp_10px.png new file mode 100644 index 00000000000..70ed56d988f Binary files /dev/null and b/totp_10px.png differ diff --git a/totp_app.c b/totp_app.c new file mode 100644 index 00000000000..a420e1ed576 --- /dev/null +++ b/totp_app.c @@ -0,0 +1,124 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lib/base32/base32.h" +#include "lib/list/list.h" +#include "lib/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 "scenes/scene_director.h" + +#define IDLE_TIMEOUT 60000 + +static void render_callback(Canvas* const canvas, void* ctx) { + PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25); + if (plugin_state != NULL && !plugin_state->changing_scene) { + 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 void totp_state_init(PluginState* const plugin_state) { + plugin_state->gui = furi_record_open(RECORD_GUI); + plugin_state->notification = furi_record_open(RECORD_NOTIFICATION); + plugin_state->dialogs = furi_record_open(RECORD_DIALOGS); + totp_config_file_load_base(plugin_state); + + totp_scene_director_init_scenes(plugin_state); + totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL); +} + +static void dispose_plugin_state(PluginState* plugin_state) { + totp_scene_director_deactivate_active_scene(plugin_state); + + totp_scene_director_dispose(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 = (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)); + + totp_state_init(plugin_state); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) { + FURI_LOG_E(LOGGING_TAG, "Cannot create mutex\r\n"); + dispose_plugin_state(plugin_state); + return 255; + } + + // 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) { + if (plugin_state->changing_scene) continue; + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + + PluginState* plugin_state = (PluginState*)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); + } else if (plugin_state->current_scene != TotpSceneAuthentication && furi_get_tick() - last_user_interaction_time > IDLE_TIMEOUT) { + totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL); + } + + view_port_update(view_port); + release_mutex(&state_mutex, 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); + dispose_plugin_state(plugin_state); + return 0; +} diff --git a/totp_app_cli.c b/totp_app_cli.c new file mode 100644 index 00000000000..ddbecb95099 --- /dev/null +++ b/totp_app_cli.c @@ -0,0 +1,32 @@ +#include +#include +#include + +void totp_cli_print_usage() { + printf("Usage:\r\n"); + printf("totp \r\n"); + printf("Cmd list:\r\n"); + printf("\tadd \t - Add new TOTP secret\r\n"); + printf("\tremove \t - Remove TOTP token\r\n"); + printf("\reset\t - Reset app to default (reset PIN and removes all tokens)\r\n"); +}; + +static void totp_cli(Cli* cli, string_t args, void* context) { + UNUSED(cli); + UNUSED(args); + UNUSED(context); + totp_cli_print_usage(); + // TODO: implement add\remove\reset +} + +void totp_on_system_start() { +#ifdef SRV_CLI + Cli* cli = furi_record_open(RECORD_CLI); + + cli_add_command(cli, "totp", CliCommandFlagDefault, totp_cli, NULL); + + furi_record_close(RECORD_CLI); +#else + UNUSED(totp_cli); +#endif +} diff --git a/types/common.h b/types/common.h new file mode 100644 index 00000000000..1fb6b573592 --- /dev/null +++ b/types/common.h @@ -0,0 +1,7 @@ +#ifndef _TOTP_COMMON_TYPES_H_ +#define _TOTP_COMMON_TYPES_H_ + +#define LOGGING_TAG "TOTP APP" +#define CRYPTO_KEY_SLOT 2 + +#endif diff --git a/types/event_type.h b/types/event_type.h new file mode 100644 index 00000000000..f828e29bdf7 --- /dev/null +++ b/types/event_type.h @@ -0,0 +1,11 @@ +#ifndef _TOTP_EVENT_TYPE_H_ +#define _TOTP_EVENT_TYPE_H_ + +#include + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +#endif diff --git a/types/plugin_event.h b/types/plugin_event.h new file mode 100644 index 00000000000..68c5bef7e96 --- /dev/null +++ b/types/plugin_event.h @@ -0,0 +1,13 @@ +#ifndef _TOTP_PLUGIN_EVENT_H_ +#define _TOTP_PLUGIN_EVENT_H_ + +#include +#include +#include "event_type.h" + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +#endif diff --git a/types/plugin_state.h b/types/plugin_state.h new file mode 100644 index 00000000000..e026d4d78ef --- /dev/null +++ b/types/plugin_state.h @@ -0,0 +1,31 @@ +#ifndef _TOTP_PLUGIN_STATE_H_ +#define _TOTP_PLUGIN_STATE_H_ + +#include +#include +#include +#include "../lib/list/list.h" +#include "../scenes/totp_scenes_enum.h" + +#define TOTP_IV_SIZE 16 + +typedef struct { + Scene current_scene; + void* current_scene_state; + bool changing_scene; + NotificationApp* notification; + DialogsApp* dialogs; + Gui* gui; + + float timezone_offset; + ListNode* tokens_list; + bool token_list_loaded; + uint8_t tokens_count; + + uint8_t* crypto_verify_data; + uint8_t crypto_verify_data_length; + uint8_t iv[TOTP_IV_SIZE]; + uint8_t base_iv[TOTP_IV_SIZE]; +} PluginState; + +#endif diff --git a/types/token_info.c b/types/token_info.c new file mode 100644 index 00000000000..7d47dd43a0b --- /dev/null +++ b/types/token_info.c @@ -0,0 +1,54 @@ +#include +#include +#include "token_info.h" +#include "stdlib.h" +#include "common.h" +#include "../lib/base32/base32.h" + +TokenInfo* token_info_alloc() { + TokenInfo* tokenInfo = malloc(sizeof(TokenInfo)); + 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); +} + +void token_info_set_secret(TokenInfo* token_info, const char* base32_token_secret, uint8_t token_secret_length, uint8_t* iv) { + uint8_t* plain_secret = malloc(token_secret_length); + int plain_secret_length = base32_decode((uint8_t *)base32_token_secret, plain_secret, token_secret_length); + token_info->token_length = plain_secret_length; + + size_t remain = token_info->token_length % 16; + if(remain) { + token_info->token_length = token_info->token_length - remain + 16; + uint8_t* plain_secret_aligned = malloc(token_info->token_length); + memcpy(plain_secret_aligned, plain_secret, plain_secret_length); + memset(plain_secret, 0, plain_secret_length); + free(plain_secret); + plain_secret = plain_secret_aligned; + } + + token_info->token = malloc(token_info->token_length); + + furi_hal_crypto_store_load_key(CRYPTO_KEY_SLOT, iv); + furi_hal_crypto_encrypt(plain_secret, token_info->token, token_info->token_length); + furi_hal_crypto_store_unload_key(CRYPTO_KEY_SLOT); + + memset(plain_secret, 0, token_info->token_length); + free(plain_secret); +} + +uint8_t token_info_get_digits_count(TokenInfo* token_info) { + switch (token_info->digits) { + case TOTP_6_DIGITS: return 6; + case TOTP_8_DIGITS: return 8; + } + + return 6; +} diff --git a/types/token_info.h b/types/token_info.h new file mode 100644 index 00000000000..b87026214c7 --- /dev/null +++ b/types/token_info.h @@ -0,0 +1,30 @@ +#ifndef _TOTP_TOKEN_INFO_H_ +#define _TOTP_TOKEN_INFO_H_ + +#include + +typedef enum { + SHA1, + SHA256, + SHA512 +} TokenHashAlgo; + +typedef enum { + TOTP_6_DIGITS, + TOTP_8_DIGITS +} TokenDigitsCount; + +typedef struct { + uint8_t* token; + uint8_t token_length; + char* name; + TokenHashAlgo algo; + TokenDigitsCount digits; +} TokenInfo; + +TokenInfo* token_info_alloc(); +void token_info_free(TokenInfo* token_info); +void token_info_set_secret(TokenInfo* token_info, const char* base32_token_secret, uint8_t token_secret_length, uint8_t* iv); +uint8_t token_info_get_digits_count(TokenInfo* token_info); + +#endif