Просмотр исходного кода

Initial commit. Moved all the code and setup flipper firmware as submodule

alex.kopachov 3 лет назад
Сommit
e85da67e10
60 измененных файлов с 4507 добавлено и 0 удалено
  1. 191 0
      .clang-format
  2. 26 0
      application.fam
  3. 95 0
      lib/base32/base32.c
  4. 39 0
      lib/base32/base32.h
  5. 331 0
      lib/config/config.c
  6. 19 0
      lib/config/config.h
  7. 19 0
      lib/config/constants.h
  8. 42 0
      lib/config/migrations/config_migration_v1_to_v2.c
  9. 8 0
      lib/config/migrations/config_migration_v1_to_v2.h
  10. 14 0
      lib/hmac/byteswap.c
  11. 9 0
      lib/hmac/byteswap.h
  12. 71 0
      lib/hmac/hmac-common.h
  13. 24 0
      lib/hmac/hmac-sha1.c
  14. 14 0
      lib/hmac/hmac-sha1.h
  15. 23 0
      lib/hmac/hmac-sha256.c
  16. 14 0
      lib/hmac/hmac-sha256.h
  17. 24 0
      lib/hmac/hmac-sha512.c
  18. 14 0
      lib/hmac/hmac-sha512.h
  19. 34 0
      lib/hmac/memxor.c
  20. 31 0
      lib/hmac/memxor.h
  21. 358 0
      lib/hmac/sha1.c
  22. 115 0
      lib/hmac/sha1.h
  23. 430 0
      lib/hmac/sha256.c
  24. 121 0
      lib/hmac/sha256.h
  25. 475 0
      lib/hmac/sha512.c
  26. 124 0
      lib/hmac/sha512.h
  27. 20 0
      lib/hmac/u64.c
  28. 42 0
      lib/hmac/u64.h
  29. 72 0
      lib/list/list.c
  30. 19 0
      lib/list/list.h
  31. 16 0
      lib/timezone_utils/timezone_utils.c
  32. 9 0
      lib/timezone_utils/timezone_utils.h
  33. 123 0
      lib/totp/totp.c
  34. 44 0
      lib/totp/totp.h
  35. 9 0
      lib/ui/constants.h
  36. 14 0
      lib/ui/icons.h
  37. 56 0
      lib/ui/ui_controls.c
  38. 11 0
      lib/ui/ui_controls.h
  39. 75 0
      scenes/add_new_token/totp_input_text.c
  40. 41 0
      scenes/add_new_token/totp_input_text.h
  41. 266 0
      scenes/add_new_token/totp_scene_add_new_token.c
  42. 21 0
      scenes/add_new_token/totp_scene_add_new_token.h
  43. 169 0
      scenes/authenticate/totp_scene_authenticate.c
  44. 17 0
      scenes/authenticate/totp_scene_authenticate.h
  45. 240 0
      scenes/generate_token/totp_scene_generate_token.c
  46. 21 0
      scenes/generate_token/totp_scene_generate_token.h
  47. 96 0
      scenes/scene_director.c
  48. 16 0
      scenes/scene_director.h
  49. 113 0
      scenes/token_menu/totp_scene_token_menu.c
  50. 21 0
      scenes/token_menu/totp_scene_token_menu.h
  51. 9 0
      scenes/totp_scenes_enum.h
  52. BIN
      totp_10px.png
  53. 124 0
      totp_app.c
  54. 32 0
      totp_app_cli.c
  55. 7 0
      types/common.h
  56. 11 0
      types/event_type.h
  57. 13 0
      types/plugin_event.h
  58. 31 0
      types/plugin_state.h
  59. 54 0
      types/token_info.c
  60. 30 0
      types/token_info.h

+ 191 - 0
.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
+...
+

+ 26 - 0
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,
+)

+ 95 - 0
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 <string.h>
+
+#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;
+}

+ 39 - 0
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 <stdint.h>
+
+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_ */

+ 331 - 0
lib/config/config.c

@@ -0,0 +1,331 @@
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#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);
+}

+ 19 - 0
lib/config/config.h

@@ -0,0 +1,19 @@
+#ifndef _TOTP_CONFIG_FILE_H_
+#define _TOTP_CONFIG_FILE_H_
+
+#include <flipper_format/flipper_format.h>
+#include <furi.h>
+#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

+ 19 - 0
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

+ 42 - 0
lib/config/migrations/config_migration_v1_to_v2.c

@@ -0,0 +1,42 @@
+#include "config_migration_v1_to_v2.h"
+#include <flipper_format/flipper_format.h>
+#include "../constants.h"
+
+#define NEW_VERSION 2
+
+bool totp_config_migrate_v1_to_v2(FlipperFormat* fff_data_file, FlipperFormat* fff_backup_data_file) {
+    flipper_format_write_header_cstr(fff_data_file, CONFIG_FILE_HEADER, NEW_VERSION);
+
+    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;
+}

+ 8 - 0
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 <flipper_format/flipper_format.h>
+
+bool totp_config_migrate_v1_to_v2(FlipperFormat* fff_data_file, FlipperFormat* fff_backup_data_file);
+
+#endif

+ 14 - 0
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);
+}

+ 9 - 0
lib/hmac/byteswap.h

@@ -0,0 +1,9 @@
+#ifndef BYTESWAP_H
+#define BYTESWAP_H
+
+#include <stdint.h>
+
+uint32_t swap_uint32( uint32_t val );
+uint64_t swap_uint64( uint64_t val );
+
+#endif

+ 71 - 0
lib/hmac/hmac-common.h

@@ -0,0 +1,71 @@
+#include <string.h>
+#include "sha256.h"
+#include "memxor.h"
+
+#define IPAD 0x36
+#define OPAD 0x5c
+
+/* Concatenate two preprocessor tokens.  */
+#define _GLHMAC_CONCAT_(prefix, suffix) prefix##suffix
+#define _GLHMAC_CONCAT(prefix, suffix) _GLHMAC_CONCAT_ (prefix, suffix)
+
+#if GL_HMAC_NAME == 5
+# define HMAC_ALG md5
+#else
+# define HMAC_ALG _GLHMAC_CONCAT (sha, GL_HMAC_NAME)
+#endif
+
+#define GL_HMAC_CTX _GLHMAC_CONCAT (HMAC_ALG, _ctx)
+#define GL_HMAC_FN _GLHMAC_CONCAT (hmac_, HMAC_ALG)
+#define GL_HMAC_FN_INIT _GLHMAC_CONCAT (HMAC_ALG, _init_ctx)
+#define GL_HMAC_FN_BLOC _GLHMAC_CONCAT (HMAC_ALG, _process_block)
+#define GL_HMAC_FN_PROC _GLHMAC_CONCAT (HMAC_ALG, _process_bytes)
+#define GL_HMAC_FN_FINI _GLHMAC_CONCAT (HMAC_ALG, _finish_ctx)
+
+static void
+hmac_hash (const void *key, size_t keylen,
+           const void *in, size_t inlen,
+           int pad, void *resbuf)
+{
+  struct GL_HMAC_CTX hmac_ctx;
+  char block[GL_HMAC_BLOCKSIZE];
+
+  memset (block, pad, sizeof block);
+  memxor (block, key, keylen);
+
+  GL_HMAC_FN_INIT (&hmac_ctx);
+  GL_HMAC_FN_BLOC (block, sizeof block, &hmac_ctx);
+  GL_HMAC_FN_PROC (in, inlen, &hmac_ctx);
+  GL_HMAC_FN_FINI (&hmac_ctx, resbuf);
+}
+
+int
+GL_HMAC_FN (const void *key, size_t keylen,
+            const void *in, size_t inlen, void *resbuf)
+{
+  char optkeybuf[GL_HMAC_HASHSIZE];
+  char innerhash[GL_HMAC_HASHSIZE];
+
+  /* Ensure key size is <= block size.  */
+  if (keylen > GL_HMAC_BLOCKSIZE)
+    {
+      struct GL_HMAC_CTX keyhash;
+
+      GL_HMAC_FN_INIT (&keyhash);
+      GL_HMAC_FN_PROC (key, keylen, &keyhash);
+      GL_HMAC_FN_FINI (&keyhash, optkeybuf);
+
+      key = optkeybuf;
+      /* zero padding of the key to the block size
+         is implicit in the memxor.  */
+      keylen = sizeof optkeybuf;
+    }
+
+  /* Compute INNERHASH from KEY and IN.  */
+  hmac_hash (key, keylen, in, inlen, IPAD, innerhash);
+
+  /* Compute result from KEY and INNERHASH.  */
+  hmac_hash (key, keylen, innerhash, sizeof innerhash, OPAD, resbuf);
+
+  return 0;
+}

+ 24 - 0
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 <https://www.gnu.org/licenses/>.  */
+
+#include "hmac-sha1.h"
+
+#include "sha1.h"
+
+#define GL_HMAC_NAME 1
+#define GL_HMAC_BLOCKSIZE 64
+#define GL_HMAC_HASHSIZE 20
+#include "hmac-common.h"

+ 14 - 0
lib/hmac/hmac-sha1.h

@@ -0,0 +1,14 @@
+#ifndef HMAC_SHA1_H
+#define HMAC_SHA1_H
+
+#include <stddef.h>
+
+#define HMAC_SHA1_RESULT_SIZE 20
+
+/* Compute Hashed Message Authentication Code with SHA-1, over BUFFER
+   data of BUFLEN bytes using the KEY of KEYLEN bytes, writing the
+   output to pre-allocated 20 byte minimum RESBUF buffer.  Return 0 on
+   success.  */
+int hmac_sha1 (const void *key, size_t keylen, const void *in, size_t inlen, void *restrict resbuf);
+
+#endif /* HMAC_SHA1_H */

+ 23 - 0
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 <https://www.gnu.org/licenses/>.  */
+
+#include "hmac-sha256.h"
+
+#define GL_HMAC_NAME 256
+#define GL_HMAC_BLOCKSIZE 64
+#define GL_HMAC_HASHSIZE 32
+
+#include "hmac-common.h"

+ 14 - 0
lib/hmac/hmac-sha256.h

@@ -0,0 +1,14 @@
+#ifndef HMAC_SHA256_H
+#define HMAC_SHA256_H
+
+#include <stddef.h>
+
+#define HMAC_SHA256_RESULT_SIZE 32
+
+/* Compute Hashed Message Authentication Code with SHA-256, over BUFFER
+   data of BUFLEN bytes using the KEY of KEYLEN bytes, writing the
+   output to pre-allocated 32 byte minimum RESBUF buffer.  Return 0 on
+   success.  */
+int hmac_sha256 (const void *key, size_t keylen, const void *in, size_t inlen, void *restrict resbuf);
+
+#endif /* HMAC_SHA256_H */

+ 24 - 0
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 <https://www.gnu.org/licenses/>.  */
+
+#include "hmac-sha512.h"
+
+#include "sha512.h"
+
+#define GL_HMAC_NAME 512
+#define GL_HMAC_BLOCKSIZE 128
+#define GL_HMAC_HASHSIZE 64
+#include "hmac-common.h"

+ 14 - 0
lib/hmac/hmac-sha512.h

@@ -0,0 +1,14 @@
+#ifndef HMAC_SHA512_H
+#define HMAC_SHA512_H
+
+#include <stddef.h>
+
+#define HMAC_SHA512_RESULT_SIZE 64
+
+/* Compute Hashed Message Authentication Code with SHA-512, over BUFFER
+   data of BUFLEN bytes using the KEY of KEYLEN bytes, writing the
+   output to pre-allocated 64 byte minimum RESBUF buffer.  Return 0 on
+   success.  */
+int hmac_sha512 (const void *key, size_t keylen, const void *in, size_t inlen, void *restrict resbuf);
+
+#endif /* HMAC_SHA512_H */

+ 34 - 0
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 <config.h> */
+
+#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;
+}

+ 31 - 0
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 <stddef.h>
+
+/* Compute binary exclusive OR of memory areas DEST and SRC, putting
+   the result in DEST, of length N bytes.  Returns a pointer to
+   DEST. */
+void *memxor (void */*restrict*/ dest, const void */*restrict*/ src, size_t n);
+
+#endif /* MEMXOR_H */

+ 358 - 0
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 <https://www.gnu.org/licenses/>.  */
+
+/* Written by Scott G. Miller
+   Credits:
+      Robert Klep <robert@ilse.nl>  -- Expansion function fix
+*/
+
+/* Specification.  */
+#if HAVE_OPENSSL_SHA1
+# define GL_OPENSSL_INLINE _GL_EXTERN_INLINE
+#endif
+#include "sha1.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#ifdef WORDS_BIGENDIAN
+# define SWAP(n) (n)
+#else
+#include "byteswap.h"
+# define SWAP(n) swap_uint32(n)
+#endif
+
+#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:
+ */

+ 115 - 0
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 <https://www.gnu.org/licenses/>.  */
+
+#ifndef SHA1_H
+# define SHA1_H 1
+
+# include <stdio.h>
+# include <stdint.h>
+
+# if HAVE_OPENSSL_SHA1
+#  ifndef OPENSSL_API_COMPAT
+#   define OPENSSL_API_COMPAT 0x10101000L /* FIXME: Use OpenSSL 1.1+ API.  */
+#  endif
+#  include <openssl/sha.h>
+# 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:
+ */

+ 430 - 0
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 <https://www.gnu.org/licenses/>.  */
+
+/* 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 <stdint.h>
+#include <string.h>
+
+#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:
+ */

+ 121 - 0
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 <https://www.gnu.org/licenses/>.  */
+
+#ifndef SHA256_H
+# define SHA256_H 1
+
+# include <stdio.h>
+# include <stdint.h>
+
+# if HAVE_OPENSSL_SHA256
+#  ifndef OPENSSL_API_COMPAT
+#   define OPENSSL_API_COMPAT 0x10101000L /* FIXME: Use OpenSSL 1.1+ API.  */
+#  endif
+#  include <openssl/sha.h>
+# 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:
+ */

+ 475 - 0
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 <https://www.gnu.org/licenses/>.  */
+
+/* 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 <stdint.h>
+#include <string.h>
+
+#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:
+ */

+ 124 - 0
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 <https://www.gnu.org/licenses/>.  */
+
+#ifndef SHA512_H
+# define SHA512_H 1
+
+# include <stdio.h>
+# include "u64.h"
+
+# if HAVE_OPENSSL_SHA512
+#  ifndef OPENSSL_API_COMPAT
+#   define OPENSSL_API_COMPAT 0x10101000L /* FIXME: Use OpenSSL 1.1+ API.  */
+#  endif
+#  include <openssl/sha.h>
+# 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:
+ */

+ 20 - 0
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 <https://www.gnu.org/licenses/>.  */
+
+#define _GL_U64_INLINE _GL_EXTERN_INLINE
+#include "u64.h"
+typedef int dummy;

+ 42 - 0
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 <https://www.gnu.org/licenses/>.  */
+
+/* Written by Paul Eggert.  */
+
+#include <stdint.h>
+
+#ifndef _GL_U64_INLINE
+# define _GL_U64_INLINE _GL_INLINE
+#endif
+
+/* Return X rotated left by N bits, where 0 < N < 64.  */
+#define u64rol(x, n) u64or (u64shl (x, n), u64shr (x, 64 - n))
+
+/* Native implementations are trivial.  See below for comments on what
+   these operations do.  */
+typedef uint64_t u64;
+# define u64hilo(hi, lo) ((u64) (((u64) (hi) << 32) + (lo)))
+# define u64init(hi, lo) u64hilo (hi, lo)
+# define u64lo(x) ((u64) (x))
+# define u64size(x) u64lo (x)
+# define u64lt(x, y) ((x) < (y))
+# define u64and(x, y) ((x) & (y))
+# define u64or(x, y) ((x) | (y))
+# define u64xor(x, y) ((x) ^ (y))
+# define u64plus(x, y) ((x) + (y))
+# define u64shl(x, n) ((x) << (n))
+# define u64shr(x, n) ((x) >> (n))

+ 72 - 0
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);
+    }
+}

+ 19 - 0
lib/list/list.h

@@ -0,0 +1,19 @@
+#ifndef _TOTP_LIST_H_
+#define _TOTP_LIST_H_
+
+#include <stdlib.h>
+#include <inttypes.h>
+
+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

+ 16 - 0
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;
+}

+ 9 - 0
lib/timezone_utils/timezone_utils.h

@@ -0,0 +1,9 @@
+#ifndef _TIMEZONE_UTILS_H_
+#define _TIMEZONE_UTILS_H_
+
+#include <inttypes.h>
+
+int32_t timezone_offset_from_hours(float hours);
+uint64_t timezone_offset_apply(uint64_t time, int32_t offset);
+
+#endif

+ 123 - 0
lib/totp/totp.c

@@ -0,0 +1,123 @@
+#include "totp.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <math.h>
+#include "../hmac/hmac-sha1.h"
+#include "../hmac/hmac-sha256.h"
+#include "../hmac/hmac-sha512.h"
+#include "../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);

+ 44 - 0
lib/totp/totp.h

@@ -0,0 +1,44 @@
+#ifndef _TOTP_H_
+#define _TOTP_H_
+
+#include <stdlib.h>
+#include <stdint.h>
+
+#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

+ 9 - 0
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

+ 14 - 0
lib/ui/icons.h

@@ -0,0 +1,14 @@
+#ifndef _TOTP_ICONS_H_
+#define _TOTP_ICONS_H_
+
+#include <inttypes.h>
+
+#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

+ 56 - 0
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);
+    }
+}

+ 11 - 0
lib/ui/ui_controls.h

@@ -0,0 +1,11 @@
+#ifndef _TOTP_UI_CONTROLS_H_
+#define _TOTP_UI_CONTROLS_H_
+
+#include <inttypes.h>
+#include <gui/gui.h>
+
+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

+ 75 - 0
scenes/add_new_token/totp_input_text.c

@@ -0,0 +1,75 @@
+#include "totp_input_text.h"
+#include <gui/view_i.h>
+#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);
+}

+ 41 - 0
scenes/add_new_token/totp_input_text.h

@@ -0,0 +1,41 @@
+#ifndef _TOTP_INPUT_TEXT_H_
+#define _TOTP_INPUT_TEXT_H_
+
+#include <gui/gui.h>
+#include <gui/view.h>
+#include <gui/modules/text_input.h>
+#include <furi.h>
+#include <furi_hal.h>
+#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

+ 266 - 0
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);
+}

+ 21 - 0
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 <gui/gui.h>
+#include <furi.h>
+#include <furi_hal.h>
+#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

+ 169 - 0
scenes/authenticate/totp_scene_authenticate.c

@@ -0,0 +1,169 @@
+#include "totp_scene_authenticate.h"
+#include <dialogs/dialogs.h>
+#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);
+}

+ 17 - 0
scenes/authenticate/totp_scene_authenticate.h

@@ -0,0 +1,17 @@
+#ifndef _TOTP_SCENE_AUTHENTICATE_H_
+#define _TOTP_SCENE_AUTHENTICATE_H_
+
+#include <gui/gui.h>
+#include <furi.h>
+#include <furi_hal.h>
+#include "../../types/plugin_state.h"
+#include "../../types/plugin_event.h"
+
+void totp_scene_authenticate_init(PluginState* plugin_state);
+void totp_scene_authenticate_activate(PluginState* plugin_state);
+void totp_scene_authenticate_render(Canvas* const canvas, PluginState* plugin_state);
+bool totp_scene_authenticate_handle_event(PluginEvent* const event, PluginState* plugin_state);
+void totp_scene_authenticate_deactivate(PluginState* plugin_state);
+void totp_scene_authenticate_free(PluginState* plugin_state);
+
+#endif

+ 240 - 0
scenes/generate_token/totp_scene_generate_token.c

@@ -0,0 +1,240 @@
+#include <gui/gui.h>
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+#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);
+}

+ 21 - 0
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 <gui/gui.h>
+#include <furi.h>
+#include <furi_hal.h>
+#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

+ 96 - 0
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;
+}

+ 16 - 0
scenes/scene_director.h

@@ -0,0 +1,16 @@
+#ifndef _SCENE_DIRECTOR_H_
+#define _SCENE_DIRECTOR_H_
+
+#include <gui/gui.h>
+#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

+ 113 - 0
scenes/token_menu/totp_scene_token_menu.c

@@ -0,0 +1,113 @@
+#include "totp_scene_token_menu.h"
+#include <gui/gui.h>
+#include <dialogs/dialogs.h>
+#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);
+}

+ 21 - 0
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 <gui/gui.h>
+#include <furi.h>
+#include <furi_hal.h>
+#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

+ 9 - 0
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


+ 124 - 0
totp_app.c

@@ -0,0 +1,124 @@
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <dialogs/dialogs.h>
+#include <stdlib.h>
+#include <flipper_format/flipper_format.h>
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+#include "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;
+}

+ 32 - 0
totp_app_cli.c

@@ -0,0 +1,32 @@
+#include <furi.h>
+#include <cli/cli.h>
+#include <storage/storage.h>
+
+void totp_cli_print_usage() {
+    printf("Usage:\r\n");
+    printf("totp <cmd> <args>\r\n");
+    printf("Cmd list:\r\n");
+    printf("\tadd <secret:string> <name:string>\t - Add new TOTP secret\r\n");
+    printf("\tremove <name:string>\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
+}

+ 7 - 0
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

+ 11 - 0
types/event_type.h

@@ -0,0 +1,11 @@
+#ifndef _TOTP_EVENT_TYPE_H_
+#define _TOTP_EVENT_TYPE_H_
+
+#include <inttypes.h>
+
+typedef enum {
+    EventTypeTick,
+    EventTypeKey,
+} EventType;
+
+#endif

+ 13 - 0
types/plugin_event.h

@@ -0,0 +1,13 @@
+#ifndef _TOTP_PLUGIN_EVENT_H_
+#define _TOTP_PLUGIN_EVENT_H_
+
+#include <inttypes.h>
+#include <input/input.h>
+#include "event_type.h"
+
+typedef struct {
+    EventType type;
+    InputEvent input;
+} PluginEvent;
+
+#endif

+ 31 - 0
types/plugin_state.h

@@ -0,0 +1,31 @@
+#ifndef _TOTP_PLUGIN_STATE_H_
+#define _TOTP_PLUGIN_STATE_H_
+
+#include <notification/notification.h>
+#include <gui/gui.h>
+#include <dialogs/dialogs.h>
+#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

+ 54 - 0
types/token_info.c

@@ -0,0 +1,54 @@
+#include <furi/furi.h>
+#include <furi_hal.h>
+#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;
+}

+ 30 - 0
types/token_info.h

@@ -0,0 +1,30 @@
+#ifndef _TOTP_TOKEN_INFO_H_
+#define _TOTP_TOKEN_INFO_H_
+
+#include <inttypes.h>
+
+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