diff --git a/ReadMe.md b/ReadMe.md index 4d880567e..895e5fc48 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -15,6 +15,7 @@ - Last Synced/Checked [OFW](https://github.com/flipperdevices/flipperzero-firmware), changes in [commits](https://github.com/flipperdevices/flipperzero-firmware/commits/dev): `2022-09-28 17:55 GMT` - Settings: Rename from SD `dolphin/name.txt` [(Thanks to E_Surge)](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/259) - New animations from stop_oxy: School Days, Whistper of the Heart and The Legend of Zelda +- Added: [TOTP (By akopachov)](https://github.com/akopachov/flipperzero-firmware/tree/totp_plugin)
TO DO
@@ -220,6 +221,7 @@ $ ./fbt plugin_dist FIRMWARE_APP_SET=ext_apps - [Spectrum Analyzer (By jolcese)](https://github.com/jolcese/flipperzero-firmware/tree/spectrum/applications/spectrum_analyzer) [Updates (for testing) Thanks to theY4Kman](https://github.com/theY4Kman/flipperzero-firmware) - [Sub-GHz Bruteforcer (By Ganapati & xMasterX)](https://github.com/Eng1n33r/flipperzero-firmware/pull/57) - [Sub-GHz Playlist (By darmiel)](https://github.com/darmiel/flipper-playlist) +- [TOTP (By akopachov)](https://github.com/akopachov/flipperzero-firmware/tree/totp_plugin) - [UPC-A Generator (By McAzzaMan)](https://github.com/McAzzaMan/flipperzero-firmware/tree/UPC-A_Barcode_Generator/applications/barcode_generator) - [USB Keyboard (By huuck)](https://github.com/huuck/FlipperZeroUSBKeyboard) - [WAV Player (By Zlo)](https://github.com/flipperdevices/flipperzero-firmware/tree/zlo/wav-player) Updated by Atmanos & RogueMaster To Work diff --git a/applications/plugins/totp/application.fam b/applications/plugins/totp/application.fam new file mode 100644 index 000000000..d8e48578b --- /dev/null +++ b/applications/plugins/totp/application.fam @@ -0,0 +1,24 @@ +App( + appid="totp", + name="Authenticator", + apptype=FlipperAppType.EXTERNAL, + entry_point="totp_app", + cdefines=["APP_TOTP"], + requires=[ + "gui", + "cli", + "dialogs" + ], + 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, +# ) \ No newline at end of file diff --git a/applications/plugins/totp/lib/base32/base32.c b/applications/plugins/totp/lib/base32/base32.c new file mode 100644 index 000000000..9c8e30236 --- /dev/null +++ b/applications/plugins/totp/lib/base32/base32.c @@ -0,0 +1,95 @@ +// Base32 implementation +// +// Copyright 2010 Google Inc. +// Author: Markus Gutschke +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "base32.h" + +int base32_decode(const uint8_t *encoded, uint8_t *result, int bufSize) { + int buffer = 0; + int bitsLeft = 0; + int count = 0; + for (const uint8_t *ptr = encoded; count < bufSize && *ptr; ++ptr) { + uint8_t ch = *ptr; + if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-') { + continue; + } + buffer <<= 5; + + // Deal with commonly mistyped characters + if (ch == '0') { + ch = 'O'; + } else if (ch == '1') { + ch = 'L'; + } else if (ch == '8') { + ch = 'B'; + } + + // Look up one base32 digit + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { + ch = (ch & 0x1F) - 1; + } else if (ch >= '2' && ch <= '7') { + ch -= '2' - 26; + } else { + return -1; + } + + buffer |= ch; + bitsLeft += 5; + if (bitsLeft >= 8) { + result[count++] = buffer >> (bitsLeft - 8); + bitsLeft -= 8; + } + } + if (count < bufSize) { + result[count] = '\000'; + } + return count; +} + +int base32_encode(const uint8_t *data, int length, uint8_t *result, + int bufSize) { + if (length < 0 || length > (1 << 28)) { + return -1; + } + int count = 0; + if (length > 0) { + int buffer = data[0]; + int next = 1; + int bitsLeft = 8; + while (count < bufSize && (bitsLeft > 0 || next < length)) { + if (bitsLeft < 5) { + if (next < length) { + buffer <<= 8; + buffer |= data[next++] & 0xFF; + bitsLeft += 8; + } else { + int pad = 5 - bitsLeft; + buffer <<= pad; + bitsLeft += pad; + } + } + int index = 0x1F & (buffer >> (bitsLeft - 5)); + bitsLeft -= 5; + result[count++] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[index]; + } + } + if (count < bufSize) { + result[count] = '\000'; + } + return count; +} diff --git a/applications/plugins/totp/lib/base32/base32.h b/applications/plugins/totp/lib/base32/base32.h new file mode 100644 index 000000000..fb4da0bee --- /dev/null +++ b/applications/plugins/totp/lib/base32/base32.h @@ -0,0 +1,39 @@ +// Base32 implementation +// +// Copyright 2010 Google Inc. +// Author: Markus Gutschke +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Encode and decode from base32 encoding using the following alphabet: +// ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 +// This alphabet is documented in RFC 4648/3548 +// +// We allow white-space and hyphens, but all other characters are considered +// invalid. +// +// All functions return the number of output bytes or -1 on error. If the +// output buffer is too small, the result will silently be truncated. + +#ifndef _BASE32_H_ +#define _BASE32_H_ + +#include + +int base32_decode(const uint8_t *encoded, uint8_t *result, int bufSize) + __attribute__((visibility("hidden"))); +int base32_encode(const uint8_t *data, int length, uint8_t *result, + int bufSize) + __attribute__((visibility("hidden"))); + +#endif /* _BASE32_H_ */ diff --git a/applications/plugins/totp/lib/config/config.c b/applications/plugins/totp/lib/config/config.c new file mode 100644 index 000000000..c9c10aebc --- /dev/null +++ b/applications/plugins/totp/lib/config/config.c @@ -0,0 +1,189 @@ +#include "config.h" +#include +#include "../../types/common.h" +#include "../../types/token_info.h" + +#define CONFIG_FILE_PATH "/ext/apps/Misc/totp.conf" +#define CONFIG_FILE_HEADER "Flipper TOTP plugin config file" +#define CONFIG_FILE_VERSION 1 + +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) { + 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 { + 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_VERSION); + float tmp_tz = 0; + flipper_format_write_float(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, &tmp_tz, 1); + 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_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_VERSION); + flipper_format_write_hex(fff_data_file, TOTP_CONFIG_KEY_BASE_IV, &plugin_state->base_iv[0], 16); + 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; + flipper_format_write_string_cstr(fff_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, token_info->name); + flipper_format_write_hex(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, token_info->token, token_info->token_length); + 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; + 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"); + return; + } + + if (!flipper_format_read_hex(fff_data_file, TOTP_CONFIG_KEY_BASE_IV, &plugin_state->base_iv[0], 16)) { + 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"); + 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 = malloc(sizeof(TokenInfo)); + + 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)) { + continue; + } + + if (secret_bytes_count == 1) { // Plain secret key + if (!flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str)) { + 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)) { + continue; + } + } + + FURI_LOG_D(LOGGING_TAG, "Found token \"%s\"", tokenInfo->name); + + if (plugin_state->tokens_list == NULL) { + plugin_state->tokens_list = list_init_head(tokenInfo); + } else { + list_add(plugin_state->tokens_list, tokenInfo); + } + + index++; + } + + plugin_state->tokens_count = index; + plugin_state->token_list_loaded = true; + + FURI_LOG_D(LOGGING_TAG, "Found %d tokens", index); + + string_clear(temp_str); + totp_close_config_file(fff_data_file); + totp_close_storage(); + + if (has_any_plain_secret) { + totp_full_save_config_file(plugin_state); + } +} + +void totp_close_config_file(FlipperFormat* file) { + if (file == NULL) return; + flipper_format_file_close(file); + flipper_format_free(file); +} diff --git a/applications/plugins/totp/lib/config/config.h b/applications/plugins/totp/lib/config/config.h new file mode 100644 index 000000000..d1912cc50 --- /dev/null +++ b/applications/plugins/totp/lib/config/config.h @@ -0,0 +1,22 @@ +#ifndef _TOTP_CONFIG_FILE_H_ +#define _TOTP_CONFIG_FILE_H_ + +#include +#include +#include "../../types/plugin_state.h" + +#define TOTP_CONFIG_KEY_TIMEZONE "Timezone" +#define TOTP_CONFIG_KEY_TOKEN_NAME "TokenName" +#define TOTP_CONFIG_KEY_TOKEN_SECRET "TokenSecret" +#define TOTP_CONFIG_KEY_CRYPTO_VERIFY "Crypto" +#define TOTP_CONFIG_KEY_BASE_IV "BaseIV" + +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); + +#endif diff --git a/applications/plugins/totp/lib/list/list.c b/applications/plugins/totp/lib/list/list.c new file mode 100644 index 000000000..08c9d9930 --- /dev/null +++ b/applications/plugins/totp/lib/list/list.c @@ -0,0 +1,72 @@ +#include "list.h" + +ListNode *list_init_head(void* data) { + ListNode *new = (ListNode *) malloc(sizeof(ListNode)); + new->data = data; + new->next = NULL; + return new; +} + +ListNode *list_add(ListNode *head, void* data) { + ListNode *new = (ListNode *) malloc(sizeof(ListNode)); + new->data = data; + new->next = NULL; + + if (head == NULL) + head = new; + else { + ListNode *it; + + for (it = head; it->next != NULL; it = it->next) + ; + + it->next = new; + } + + return head; +} + +ListNode *list_find(ListNode *head, void* data) { + ListNode *it; + + for (it = head; it != NULL; it = it->next) + if (it->data == data) + break; + + return it; +} + +ListNode *list_element_at(ListNode *head, uint16_t index) { + ListNode *it; + uint16_t i; + for (it = head, i = 0; it != NULL && i < index; it = it->next, i++); + return it; +} + +ListNode *list_remove(ListNode *head, ListNode *ep) { + if (head == ep) { + ListNode *new_head = head->next; + free(head); + return new_head; + } + + ListNode *it; + + for (it = head; it->next != ep; it = it->next) + ; + + it->next = ep->next; + free(ep); + + return head; +} + +void list_free(ListNode *head) { + ListNode *it = head, *tmp; + + while (it != NULL) { + tmp = it; + it = it->next; + free(tmp); + } +} diff --git a/applications/plugins/totp/lib/list/list.h b/applications/plugins/totp/lib/list/list.h new file mode 100644 index 000000000..4d0bbd6f8 --- /dev/null +++ b/applications/plugins/totp/lib/list/list.h @@ -0,0 +1,19 @@ +#ifndef _TOTP_LIST_H_ +#define _TOTP_LIST_H_ + +#include +#include + +typedef struct ListNode { + void* data; + struct ListNode *next; +} ListNode; + +ListNode *list_init_head(void* data); +ListNode *list_add(ListNode *head, void* data); /* adds element with specified data to the end of the list and returns new head node. */ +ListNode *list_find(ListNode *head, void* data); /* returns pointer of element with specified data in list. */ +ListNode *list_element_at(ListNode *head, uint16_t index); /* returns pointer of element with specified index in list. */ +ListNode *list_remove(ListNode *head, ListNode *ep); /* removes element from the list and returns new head node. */ +void list_free(ListNode *head); /* deletes all elements of the list. */ + +#endif diff --git a/applications/plugins/totp/lib/totp/sha1.c b/applications/plugins/totp/lib/totp/sha1.c new file mode 100644 index 000000000..cdcd87bdf --- /dev/null +++ b/applications/plugins/totp/lib/totp/sha1.c @@ -0,0 +1,169 @@ +#include +#include "sha1.h" + +union _buffer { + uint8_t b[BLOCK_LENGTH]; + uint32_t w[BLOCK_LENGTH/4]; +} buffer; +union _state { + uint8_t b[HASH_LENGTH]; + uint32_t w[HASH_LENGTH/4]; +} state; + +uint8_t bufferOffset; +uint32_t byteCount; +uint8_t keyBuffer[BLOCK_LENGTH]; +uint8_t innerHash[HASH_LENGTH]; + +#define SHA1_K0 0x5a827999 +#define SHA1_K20 0x6ed9eba1 +#define SHA1_K40 0x8f1bbcdc +#define SHA1_K60 0xca62c1d6 + +uint8_t sha1InitState[] = { + 0x01,0x23,0x45,0x67, // H0 + 0x89,0xab,0xcd,0xef, // H1 + 0xfe,0xdc,0xba,0x98, // H2 + 0x76,0x54,0x32,0x10, // H3 + 0xf0,0xe1,0xd2,0xc3 // H4 +}; + +void sha1_init(void) { + memcpy(state.b,sha1InitState,HASH_LENGTH); + byteCount = 0; + bufferOffset = 0; +} + +uint32_t rol32(uint32_t number, uint8_t bits) { + return ((number << bits) | (uint32_t)(number >> (32-bits))); +} + +void hashBlock() { + uint8_t i; + uint32_t a,b,c,d,e,t; + + a=state.w[0]; + b=state.w[1]; + c=state.w[2]; + d=state.w[3]; + e=state.w[4]; + for (i=0; i<80; i++) { + if (i>=16) { + t = buffer.w[(i+13)&15] ^ buffer.w[(i+8)&15] ^ buffer.w[(i+2)&15] ^ buffer.w[i&15]; + buffer.w[i&15] = rol32(t,1); + } + if (i<20) { + t = (d ^ (b & (c ^ d))) + SHA1_K0; + } else if (i<40) { + t = (b ^ c ^ d) + SHA1_K20; + } else if (i<60) { + t = ((b & c) | (d & (b | c))) + SHA1_K40; + } else { + t = (b ^ c ^ d) + SHA1_K60; + } + t+=rol32(a,5) + e + buffer.w[i&15]; + e=d; + d=c; + c=rol32(b,30); + b=a; + a=t; + } + state.w[0] += a; + state.w[1] += b; + state.w[2] += c; + state.w[3] += d; + state.w[4] += e; +} + +void addUncounted(uint8_t data) { + buffer.b[bufferOffset ^ 3] = data; + bufferOffset++; + if (bufferOffset == BLOCK_LENGTH) { + hashBlock(); + bufferOffset = 0; + } +} + +void sha1_write(uint8_t data) { + ++byteCount; + addUncounted(data); + + return; +} + +void sha1_write_array(uint8_t *buffer, uint8_t size){ + while (size--) { + sha1_write(*buffer++); + } +} + +void pad() { + // Implement SHA-1 padding (fips180-2 ��5.1.1) + + // Pad with 0x80 followed by 0x00 until the end of the block + addUncounted(0x80); + while (bufferOffset != 56) addUncounted(0x00); + + // Append length in the last 8 bytes + addUncounted(0); // We're only using 32 bit lengths + addUncounted(0); // But SHA-1 supports 64 bit lengths + addUncounted(0); // So zero pad the top bits + addUncounted(byteCount >> 29); // Shifting to multiply by 8 + addUncounted(byteCount >> 21); // as SHA-1 supports bitstreams as well as + addUncounted(byteCount >> 13); // byte. + addUncounted(byteCount >> 5); + addUncounted(byteCount << 3); +} + +uint8_t* sha1_result(void) { + // Pad to complete the last block + pad(); + + // Swap byte order back + uint8_t i; + for (i=0; i<5; i++) { + uint32_t a,b; + a=state.w[i]; + b=a<<24; + b|=(a<<8) & 0x00ff0000; + b|=(a>>8) & 0x0000ff00; + b|=a>>24; + state.w[i]=b; + } + + // Return pointer to hash (20 characters) + return state.b; +} + +#define HMAC_IPAD 0x36 +#define HMAC_OPAD 0x5c + +void sha1_init_hmac(const uint8_t* key, uint8_t keyLength) { + uint8_t i; + memset(keyBuffer,0,BLOCK_LENGTH); + if (keyLength > BLOCK_LENGTH) { + // Hash long keys + sha1_init(); + for (;keyLength--;) sha1_write(*key++); + memcpy(keyBuffer,sha1_result(),HASH_LENGTH); + } else { + // Block length keys are used as is + memcpy(keyBuffer,key,keyLength); + } + // Start inner hash + sha1_init(); + for (i=0; i + +#define HASH_LENGTH 20 +#define BLOCK_LENGTH 64 + +void sha1_init(void); +void sha1_init_hmac(const uint8_t* secret, uint8_t secretLength); +uint8_t* sha1_result(void); +uint8_t* sha1_result_hmac(void); +void sha1_write(uint8_t); +void sha1_write_array(uint8_t *buffer, uint8_t size); +#endif diff --git a/applications/plugins/totp/lib/totp/totp.c b/applications/plugins/totp/lib/totp/totp.c new file mode 100644 index 000000000..313060671 --- /dev/null +++ b/applications/plugins/totp/lib/totp/totp.c @@ -0,0 +1,71 @@ +#include "totp.h" +#include "sha1.h" + +int _timeZoneOffset; +uint32_t _timeStep; + +// Init the library with the private key, its length and the timeStep duration +void totp_setup(uint32_t timeStep) { + _timeStep = timeStep; +} + +void totp_set_timezone(float timezone){ + _timeZoneOffset = (int)(timezone * 3600.0f); +} + +// Generate a code, using the number of steps provided +uint32_t totp_get_code_from_steps(uint8_t* hmacKey, uint8_t keyLength, uint32_t steps) { + // STEP 0, map the number of steps in a 8-bytes array (counter value) + uint8_t _byteArray[8]; + _byteArray[0] = 0x00; + _byteArray[1] = 0x00; + _byteArray[2] = 0x00; + _byteArray[3] = 0x00; + _byteArray[4] = (uint8_t)((steps >> 24) & 0xFF); + _byteArray[5] = (uint8_t)((steps >> 16) & 0xFF); + _byteArray[6] = (uint8_t)((steps >> 8) & 0XFF); + _byteArray[7] = (uint8_t)((steps & 0XFF)); + + // STEP 1, get the HMAC-SHA1 hash from counter and key + sha1_init_hmac(hmacKey, keyLength); + sha1_write_array(_byteArray, 8); + uint8_t* _hash = sha1_result_hmac(); + + // STEP 2, apply dynamic truncation to obtain a 4-bytes string + uint32_t _truncatedHash = 0; + uint8_t _offset = _hash[20 - 1] & 0xF; + uint8_t j; + for (j = 0; j < 4; ++j) { + _truncatedHash <<= 8; + _truncatedHash |= _hash[_offset + j]; + } + + // STEP 3, compute the OTP value + _truncatedHash &= 0x7FFFFFFF; //Disabled + _truncatedHash %= 1000000; + + return _truncatedHash; +} + +uint32_t time_struct_to_timestamp(struct tm time){ + //time.tm_mon -= 1; + //time.tm_year -= 1900; + return mktime(&(time)) - 2208988800; +} + +// Generate a code, using the timestamp provided +uint32_t totp_get_code_from_timestamp(uint8_t* hmacKey, uint8_t keyLength, uint32_t timeStamp) { + uint32_t steps; + if (_timeZoneOffset >= 0) { + steps = (timeStamp - _timeZoneOffset) / _timeStep; + } else { + steps = (timeStamp + (-_timeZoneOffset)) / _timeStep; + } + + return totp_get_code_from_steps(hmacKey, keyLength, steps); +} + +// Generate a code, using the timestamp provided +uint32_t totp_get_code_from_time_struct(uint8_t* hmacKey, uint8_t keyLength, struct tm time) { + return totp_get_code_from_timestamp(hmacKey, keyLength, time_struct_to_timestamp(time)); +} diff --git a/applications/plugins/totp/lib/totp/totp.h b/applications/plugins/totp/lib/totp/totp.h new file mode 100644 index 000000000..fab975a81 --- /dev/null +++ b/applications/plugins/totp/lib/totp/totp.h @@ -0,0 +1,11 @@ +#ifndef _token_h +#define _token_h + +#include +#include + +void totp_setup(uint32_t timeStep); +void totp_set_timezone(float timezone); +uint32_t totp_get_code_from_timestamp(uint8_t* hmacKey, uint8_t keyLength, uint32_t timeStamp); +uint32_t totp_get_code_from_time_struct(uint8_t* hmacKey, uint8_t keyLength, struct tm time); +#endif diff --git a/applications/plugins/totp/lib/ui/canvas_extensions.c b/applications/plugins/totp/lib/ui/canvas_extensions.c new file mode 100644 index 000000000..9245c02ac --- /dev/null +++ b/applications/plugins/totp/lib/ui/canvas_extensions.c @@ -0,0 +1,11 @@ +#include "canvas_extensions.h" + +void canvas_draw_dots(Canvas* const canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height, const uint8_t *dots) { + for (uint8_t i = 0; i < width; i++) { + for (uint8_t j = 0; j < height; j++) { + if (dots[i + j * width]) { + canvas_draw_dot(canvas, x + i, y + j); + } + } + } +} diff --git a/applications/plugins/totp/lib/ui/canvas_extensions.h b/applications/plugins/totp/lib/ui/canvas_extensions.h new file mode 100644 index 000000000..ab1714d4a --- /dev/null +++ b/applications/plugins/totp/lib/ui/canvas_extensions.h @@ -0,0 +1,11 @@ +#ifndef _TOTPCANVAS_EXTENSIONS_H_ +#define _TOTPCANVAS_EXTENSIONS_H_ + +#include +#include +#include +#include + +void canvas_draw_dots(Canvas* const canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height, const uint8_t *dots); + +#endif diff --git a/applications/plugins/totp/lib/ui/constants.h b/applications/plugins/totp/lib/ui/constants.h new file mode 100644 index 000000000..658686c8d --- /dev/null +++ b/applications/plugins/totp/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 64 +#define SCREEN_HEIGHT_CENTER 32 + +#endif diff --git a/applications/plugins/totp/lib/ui/icons.h b/applications/plugins/totp/lib/ui/icons.h new file mode 100644 index 000000000..8f60bafa0 --- /dev/null +++ b/applications/plugins/totp/lib/ui/icons.h @@ -0,0 +1,30 @@ +#ifndef _TOTP_ICONS_H_ +#define _TOTP_ICONS_H_ + +#include + +static const uint8_t ICON_ARROW_LEFT_8x9[] = { + 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 1, +}; + +static const uint8_t ICON_ARROW_RIGHT_8x9[] = { + 1, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 0, 0, 0, + 1, 1, 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, +}; + +#endif diff --git a/applications/plugins/totp/lib/ui/ui_controls.c b/applications/plugins/totp/lib/ui/ui_controls.c new file mode 100644 index 000000000..15ac3eaa4 --- /dev/null +++ b/applications/plugins/totp/lib/ui/ui_controls.c @@ -0,0 +1,30 @@ +#include "ui_controls.h" +#include "constants.h" + +#define TEXT_BOX_HEIGHT 13 + +void ui_control_text_box_render(Canvas* const canvas, uint8_t y, char* text, bool is_selected) { + const uint8_t TEXT_BOX_MARGIN = 4; + 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_button_render(Canvas* const canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height, char* text, bool is_selected) { + if (is_selected) { + canvas_draw_rbox(canvas, x, y, width, height, 1); + canvas_set_color(canvas, ColorWhite); + } else { + canvas_draw_rframe(canvas, x, y, width, height, 1); + } + + canvas_draw_str_aligned(canvas, x + (width >> 1), y + (height >> 1) + 1, AlignCenter, AlignCenter, text); + if (is_selected) { + canvas_set_color(canvas, ColorBlack); + } +} diff --git a/applications/plugins/totp/lib/ui/ui_controls.h b/applications/plugins/totp/lib/ui/ui_controls.h new file mode 100644 index 000000000..41f643a0a --- /dev/null +++ b/applications/plugins/totp/lib/ui/ui_controls.h @@ -0,0 +1,10 @@ +#ifndef _TOTP_UI_CONTROLS_H_ +#define _TOTP_UI_CONTROLS_H_ + +#include +#include + +void ui_control_text_box_render(Canvas* const canvas, uint8_t y, char* text, bool is_selected); +void ui_control_button_render(Canvas* const canvas, uint8_t x, uint8_t y, uint8_t width, uint8_t height, char* text, bool is_selected); + +#endif diff --git a/applications/plugins/totp/scenes/add_new_token/totp_input_text.c b/applications/plugins/totp/scenes/add_new_token/totp_input_text.c new file mode 100644 index 000000000..2cf2c9c14 --- /dev/null +++ b/applications/plugins/totp/scenes/add_new_token/totp_input_text.c @@ -0,0 +1,75 @@ +#include "totp_input_text.h" +#include +#include "../../types/common.h" + +void view_draw(View* view, Canvas* canvas) { + furi_assert(view); + if(view->draw_callback) { + void* data = view_get_model(view); + view->draw_callback(canvas, data); + view_unlock_model(view); + } +} + +bool view_input(View* view, InputEvent* event) { + furi_assert(view); + if(view->input_callback) { + return view->input_callback(event, view->context); + } else { + return false; + } +} + +void view_unlock_model(View* view) { + furi_assert(view); + if(view->model_type == ViewModelTypeLocking) { + ViewModelLocking* model = (ViewModelLocking*)(view->model); + furi_check(furi_mutex_release(model->mutex) == FuriStatusOk); + } +} + +static void commit_text_input_callback(void* context) { + InputTextSceneState* text_input_state = (InputTextSceneState *)context; + if (text_input_state->callback != 0) { + InputTextSceneCallbackResult* result = malloc(sizeof(InputTextSceneCallbackResult)); + result->user_input_length = strlen(text_input_state->text_input_buffer); + result->user_input = malloc(result->user_input_length + 1); + result->callback_data = text_input_state->callback_data; + strcpy(result->user_input, text_input_state->text_input_buffer); + text_input_state->callback(result); + } +} + +InputTextSceneState* totp_input_text_activate(InputTextSceneContext* context) { + InputTextSceneState* text_input_state = malloc(sizeof(InputTextSceneState)); + text_input_state->text_input = text_input_alloc(); + text_input_state->text_input_view = text_input_get_view(text_input_state->text_input); + text_input_state->callback = context->callback; + text_input_state->callback_data = context->callback_data; + text_input_set_header_text(text_input_state->text_input, context->header_text); + text_input_set_result_callback( + text_input_state->text_input, + commit_text_input_callback, + text_input_state, + &text_input_state->text_input_buffer[0], + INPUT_BUFFER_SIZE, + true); + return text_input_state; +} + +void totp_input_text_render(Canvas* const canvas, InputTextSceneState* text_input_state) { + view_draw(text_input_state->text_input_view, canvas); +} + +bool totp_input_text_handle_event(PluginEvent* const event, InputTextSceneState* text_input_state) { + if(event->type == EventTypeKey) { + view_input(text_input_state->text_input_view, &event->input); + } + + return true; +} + +void totp_input_text_free(InputTextSceneState* state) { + text_input_free(state->text_input); + free(state); +} diff --git a/applications/plugins/totp/scenes/add_new_token/totp_input_text.h b/applications/plugins/totp/scenes/add_new_token/totp_input_text.h new file mode 100644 index 000000000..945835fce --- /dev/null +++ b/applications/plugins/totp/scenes/add_new_token/totp_input_text.h @@ -0,0 +1,41 @@ +#ifndef _TOTP_INPUT_TEXT_H_ +#define _TOTP_INPUT_TEXT_H_ + +#include +#include +#include +#include +#include +#include "../../types/plugin_state.h" +#include "../../types/plugin_event.h" + +typedef struct { + char* user_input; + uint8_t user_input_length; + void* callback_data; +} InputTextSceneCallbackResult; + +typedef void (*InputTextSceneCallback)(InputTextSceneCallbackResult* result); + +typedef struct { + InputTextSceneCallback callback; + char* header_text; + void* callback_data; +} InputTextSceneContext; + +#define INPUT_BUFFER_SIZE 255 + +typedef struct { + TextInput* text_input; + View* text_input_view; + char text_input_buffer[INPUT_BUFFER_SIZE]; + InputTextSceneCallback callback; + void* callback_data; +} InputTextSceneState; + +InputTextSceneState* totp_input_text_activate(InputTextSceneContext* context); +void totp_input_text_render(Canvas* const canvas, InputTextSceneState* text_input_state); +bool totp_input_text_handle_event(PluginEvent* const event, InputTextSceneState* text_input_state); +void totp_input_text_free(InputTextSceneState* state); + +#endif diff --git a/applications/plugins/totp/scenes/add_new_token/totp_scene_add_new_token.c b/applications/plugins/totp/scenes/add_new_token/totp_scene_add_new_token.c new file mode 100644 index 000000000..73c9231e1 --- /dev/null +++ b/applications/plugins/totp/scenes/add_new_token/totp_scene_add_new_token.c @@ -0,0 +1,205 @@ +#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" + +typedef enum { + TokenNameTextBox, + TokenSecretTextBox, + 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; +} 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->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; + } + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, "Add new token"); + + canvas_set_font(canvas, FontSecondary); + ui_control_text_box_render(canvas, 10, scene_state->token_name, scene_state->selected_control == TokenNameTextBox); + ui_control_text_box_render(canvas, 27, scene_state->token_secret, scene_state->selected_control == TokenSecretTextBox); + ui_control_button_render(canvas, SCREEN_WIDTH_CENTER - 24, 48, 48, 13, "Confirm", scene_state->selected_control == ConfirmButton); +} + +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--; + } + break; + case InputKeyDown: + if (scene_state->selected_control < ConfirmButton) { + scene_state->selected_control++; + } + break; + case InputKeyRight: + break; + case InputKeyLeft: + 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 ConfirmButton: { + TokenInfo* tokenInfo = malloc(sizeof(TokenInfo)); + 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]); + + 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); + flipper_format_write_string_cstr(cfg_file, TOTP_CONFIG_KEY_TOKEN_NAME, tokenInfo->name); + flipper_format_write_hex(cfg_file, TOTP_CONFIG_KEY_TOKEN_SECRET, tokenInfo->token, tokenInfo->token_length); + + totp_close_config_file(cfg_file); + totp_close_storage(); + + GenerateTokenSceneContext generate_scene_context = { .current_token_index = plugin_state->tokens_count - 1 }; + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, &generate_scene_context); + break; + } + } + break; + case InputKeyBack: + if (scene_state->current_token_index >= 0) { + GenerateTokenSceneContext generate_scene_context = { .current_token_index = scene_state->current_token_index }; + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, &generate_scene_context); + } else { + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } + break; + } + } + } + return true; +} + +void totp_scene_add_new_token_deactivate(PluginState* plugin_state) { + if (plugin_state->current_scene_state == NULL) return; + SceneState* scene_state = (SceneState *)plugin_state->current_scene_state; + free(scene_state->token_name); + free(scene_state->token_secret); + + free(scene_state->token_name_input_context->header_text); + free(scene_state->token_name_input_context); + + free(scene_state->token_secret_input_context->header_text); + free(scene_state->token_secret_input_context); + + if (scene_state->input_state != NULL) { + totp_input_text_free(scene_state->input_state); + } + + free(plugin_state->current_scene_state); + plugin_state->current_scene_state = NULL; +} + +void totp_scene_add_new_token_free(PluginState* plugin_state) { + UNUSED(plugin_state); +} diff --git a/applications/plugins/totp/scenes/add_new_token/totp_scene_add_new_token.h b/applications/plugins/totp/scenes/add_new_token/totp_scene_add_new_token.h new file mode 100644 index 000000000..653e17319 --- /dev/null +++ b/applications/plugins/totp/scenes/add_new_token/totp_scene_add_new_token.h @@ -0,0 +1,21 @@ +#ifndef _TOTP_SCENE_ADD_NEW_TOKEN_H_ +#define _TOTP_SCENE_ADD_NEW_TOKEN_H_ + +#include +#include +#include +#include "../../types/plugin_state.h" +#include "../../types/plugin_event.h" + +typedef struct { + uint8_t current_token_index; +} TokenAddEditSceneContext; + +void totp_scene_add_new_token_init(PluginState* plugin_state); +void totp_scene_add_new_token_activate(PluginState* plugin_state, const TokenAddEditSceneContext* context); +void totp_scene_add_new_token_render(Canvas* const canvas, PluginState* plugin_state); +bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState* plugin_state); +void totp_scene_add_new_token_deactivate(PluginState* plugin_state); +void totp_scene_add_new_token_free(PluginState* plugin_state); + +#endif diff --git a/applications/plugins/totp/scenes/authenticate/totp_scene_authenticate.c b/applications/plugins/totp/scenes/authenticate/totp_scene_authenticate.c new file mode 100644 index 000000000..ee930cdfe --- /dev/null +++ b/applications/plugins/totp/scenes/authenticate/totp_scene_authenticate.c @@ -0,0 +1,159 @@ +#include "totp_scene_authenticate.h" +#include "../../types/common.h" +#include "../../lib/ui/icons.h" +#include "../../lib/ui/canvas_extensions.h" +#include "../../lib/ui/constants.h" +#include "../../lib/config/config.h" +#include "../scene_director.h" +#include "../totp_scenes_enum.h" + +#define MAX_CODE_LENGTH 16 +#define CRYPTO_VERIFY_KEY "FFF_Crypto_pass" +#define CRYPTO_VERIFY_KEY_LENGTH 16 +#define BASE_IV_LENGTH 16 + +typedef struct { + uint8_t code_input[MAX_CODE_LENGTH]; + uint8_t code_length; +} SceneState; + +void totp_scene_authenticate_init(PluginState* plugin_state) { + for (uint8_t i = 0; i < MAX_CODE_LENGTH; i++) plugin_state->iv[i] = 0; +} + +void totp_scene_authenticate_activate(PluginState* plugin_state) { + SceneState* scene_state = malloc(sizeof(SceneState)); + scene_state->code_length = 0; + for (uint8_t i = 0; i < MAX_CODE_LENGTH; i++) scene_state->code_input[i] = 0; + 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; + if (plugin_state->crypto_verify_data == NULL) { + canvas_draw_str_aligned(canvas, SCREEN_WIDTH_CENTER, SCREEN_HEIGHT_CENTER - 20, AlignCenter, AlignCenter, "Use arrow keys"); + canvas_draw_str_aligned(canvas, SCREEN_WIDTH_CENTER, SCREEN_HEIGHT_CENTER - 5, AlignCenter, AlignCenter, "to setup new PIN"); + } else { + canvas_draw_str_aligned(canvas, SCREEN_WIDTH_CENTER, SCREEN_HEIGHT_CENTER - 10, 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], BASE_IV_LENGTH); + } + + memcpy(&plugin_state->iv[0], &plugin_state->base_iv[0], BASE_IV_LENGTH); + 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, BASE_IV_LENGTH); + 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"); + for (uint8_t i = 0; i < MAX_CODE_LENGTH; i++) { + scene_state->code_input[i] = 0; + plugin_state->iv[i] = 0; + } + scene_state->code_length = 0; + } + break; + case InputKeyBack: + if (scene_state->code_length > 0) { + scene_state->code_input[scene_state->code_length - 1] = 0; + scene_state->code_length--; + } + break; + } + } + } + + return true; +} + +void totp_scene_authenticate_deactivate(PluginState* plugin_state) { + if (plugin_state->current_scene_state == NULL) return; + free(plugin_state->current_scene_state); + plugin_state->current_scene_state = NULL; +} + +void totp_scene_authenticate_free(PluginState* plugin_state) { + UNUSED(plugin_state); +} diff --git a/applications/plugins/totp/scenes/authenticate/totp_scene_authenticate.h b/applications/plugins/totp/scenes/authenticate/totp_scene_authenticate.h new file mode 100644 index 000000000..d301c0c34 --- /dev/null +++ b/applications/plugins/totp/scenes/authenticate/totp_scene_authenticate.h @@ -0,0 +1,17 @@ +#ifndef _TOTP_SCENE_AUTHENTICATE_H_ +#define _TOTP_SCENE_AUTHENTICATE_H_ + +#include +#include +#include +#include "../../types/plugin_state.h" +#include "../../types/plugin_event.h" + +void totp_scene_authenticate_init(PluginState* plugin_state); +void totp_scene_authenticate_activate(PluginState* plugin_state); +void totp_scene_authenticate_render(Canvas* const canvas, PluginState* plugin_state); +bool totp_scene_authenticate_handle_event(PluginEvent* const event, PluginState* plugin_state); +void totp_scene_authenticate_deactivate(PluginState* plugin_state); +void totp_scene_authenticate_free(PluginState* plugin_state); + +#endif diff --git a/applications/plugins/totp/scenes/generate_token/totp_scene_generate_token.c b/applications/plugins/totp/scenes/generate_token/totp_scene_generate_token.c new file mode 100644 index 000000000..0bfe65d5f --- /dev/null +++ b/applications/plugins/totp/scenes/generate_token/totp_scene_generate_token.c @@ -0,0 +1,215 @@ +#include +#include +#include +#include "totp_scene_generate_token.h" +#include "../../types/token_info.h" +#include "../../types/common.h" +#include "../../lib/ui/icons.h" +#include "../../lib/ui/canvas_extensions.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 + +typedef struct { + uint8_t current_token_index; + char last_code[7]; + 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) { + str[6] = '\0'; + if (i_token_code == 0 || i_token_code > 999999) { + str[5] = '-'; + str[4] = '-'; + str[3] = '-'; + str[2] = '-'; + str[1] = '-'; + str[0] = '-'; + } else { + str[5] = i_token_code % 10 + 0x30; + str[4] = (i_token_code = i_token_code / 10) % 10 + 0x30; + str[3] = (i_token_code = i_token_code / 10) % 10 + 0x30; + str[2] = (i_token_code = i_token_code / 10) % 10 + 0x30; + str[1] = (i_token_code = i_token_code / 10) % 10 + 0x30; + str[0] = (i_token_code = i_token_code / 10) % 10 + 0x30; + } +} + +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); + totp_setup(TOKEN_LIFETIME); +} + +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; + totp_set_timezone(plugin_state->timezone_offset); + 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_get_code_from_timestamp(key, tokenInfo->token_length, curr_ts), scene_state->last_code); + 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_dots(canvas, 0, SCREEN_HEIGHT_CENTER - 24, 8, 9, ICON_ARROW_LEFT_8x9); + canvas_draw_dots(canvas, SCREEN_WIDTH - 9, SCREEN_HEIGHT_CENTER - 24, 8, 9, ICON_ARROW_RIGHT_8x9); + } +} + +bool totp_scene_generate_token_handle_event(PluginEvent* const event, PluginState* plugin_state) { + if(event->type == EventTypeKey) { + if (event->input.type == InputTypeLong && event->input.key == InputKeyBack) { + return false; + } else if(event->input.type == InputTypePress) { + SceneState* scene_state = (SceneState *)plugin_state->current_scene_state; + switch(event->input.key) { + case InputKeyUp: + break; + case InputKeyDown: + break; + case InputKeyRight: + if (scene_state->current_token_index < plugin_state->tokens_count - 1) { + scene_state->current_token_index++; + } else { + scene_state->current_token_index = 0; + } + update_totp_params(plugin_state); + break; + case InputKeyLeft: + if (scene_state->current_token_index > 0) { + scene_state->current_token_index--; + } else { + scene_state->current_token_index = plugin_state->tokens_count - 1; + } + update_totp_params(plugin_state); + break; + case InputKeyOk: + if (plugin_state->tokens_count == 0) { + totp_scene_director_activate_scene(plugin_state, TotpSceneAddNewToken, NULL); + } else { + TokenMenuSceneContext ctx = { .current_token_index = scene_state->current_token_index }; + totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, &ctx); + } + break; + case InputKeyBack: + break; + } + } + } + + return true; +} + +void totp_scene_generate_token_deactivate(PluginState* plugin_state) { + if (plugin_state->current_scene_state == NULL) return; + SceneState* scene_state = (SceneState *)plugin_state->current_scene_state; + + free(scene_state->last_code); + free(scene_state); + plugin_state->current_scene_state = NULL; +} + +void totp_scene_generate_token_free(PluginState* plugin_state) { + UNUSED(plugin_state); +} diff --git a/applications/plugins/totp/scenes/generate_token/totp_scene_generate_token.h b/applications/plugins/totp/scenes/generate_token/totp_scene_generate_token.h new file mode 100644 index 000000000..40dc47a69 --- /dev/null +++ b/applications/plugins/totp/scenes/generate_token/totp_scene_generate_token.h @@ -0,0 +1,21 @@ +#ifndef _TOTP_SCENE_GENERATE_TOKEN_H_ +#define _TOTP_SCENE_GENERATE_TOKEN_H_ + +#include +#include +#include +#include "../../types/plugin_state.h" +#include "../../types/plugin_event.h" + +typedef struct { + uint8_t current_token_index; +} GenerateTokenSceneContext; + +void totp_scene_generate_token_init(PluginState* plugin_state); +void totp_scene_generate_token_activate(PluginState* plugin_state, const GenerateTokenSceneContext* context); +void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_state); +bool totp_scene_generate_token_handle_event(PluginEvent* const event, PluginState* plugin_state); +void totp_scene_generate_token_deactivate(PluginState* plugin_state); +void totp_scene_generate_token_free(PluginState* plugin_state); + +#endif diff --git a/applications/plugins/totp/scenes/scene_director.c b/applications/plugins/totp/scenes/scene_director.c new file mode 100644 index 000000000..428df9080 --- /dev/null +++ b/applications/plugins/totp/scenes/scene_director.c @@ -0,0 +1,96 @@ +#include "../types/common.h" +#include "scene_director.h" +#include "authenticate/totp_scene_authenticate.h" +#include "generate_token/totp_scene_generate_token.h" +#include "add_new_token/totp_scene_add_new_token.h" +#include "token_menu/totp_scene_token_menu.h" + +void totp_scene_director_activate_scene(PluginState* const plugin_state, Scene scene, const void* context) { + plugin_state->changing_scene = true; + totp_scene_director_deactivate_active_scene(plugin_state); + switch (scene) { + case TotpSceneGenerateToken: + totp_scene_generate_token_activate(plugin_state, context); + break; + case TotpSceneAuthentication: + totp_scene_authenticate_activate(plugin_state); + break; + case TotpSceneAddNewToken: + totp_scene_add_new_token_activate(plugin_state, context); + break; + case TotpSceneTokenMenu: + totp_scene_token_menu_activate(plugin_state, context); + break; + } + + plugin_state->current_scene = scene; + plugin_state->changing_scene = false; +} + +void totp_scene_director_deactivate_active_scene(PluginState* const plugin_state) { + switch (plugin_state->current_scene) { + case TotpSceneGenerateToken: + totp_scene_generate_token_deactivate(plugin_state); + break; + case TotpSceneAuthentication: + totp_scene_authenticate_deactivate(plugin_state); + break; + case TotpSceneAddNewToken: + totp_scene_add_new_token_deactivate(plugin_state); + break; + case TotpSceneTokenMenu: + totp_scene_token_menu_deactivate(plugin_state); + break; + } +} + +void totp_scene_director_init_scenes(PluginState* const plugin_state) { + totp_scene_authenticate_init(plugin_state); + totp_scene_generate_token_init(plugin_state); + totp_scene_add_new_token_init(plugin_state); + totp_scene_token_menu_init(plugin_state); +} + +void totp_scene_director_render(Canvas* const canvas, PluginState* const plugin_state) { + switch (plugin_state->current_scene) { + case TotpSceneGenerateToken: + totp_scene_generate_token_render(canvas, plugin_state); + break; + case TotpSceneAuthentication: + totp_scene_authenticate_render(canvas, plugin_state); + break; + case TotpSceneAddNewToken: + totp_scene_add_new_token_render(canvas, plugin_state); + break; + case TotpSceneTokenMenu: + totp_scene_token_menu_render(canvas, plugin_state); + break; + } +} + +void totp_scene_director_dispose(PluginState* const plugin_state) { + totp_scene_generate_token_free(plugin_state); + totp_scene_authenticate_free(plugin_state); + totp_scene_add_new_token_free(plugin_state); + totp_scene_token_menu_free(plugin_state); +} + +bool totp_scene_director_handle_event(PluginEvent* const event, PluginState* const plugin_state) { + bool processing = true; + switch (plugin_state->current_scene) { + case TotpSceneGenerateToken: + processing = totp_scene_generate_token_handle_event(event, plugin_state); + break; + case TotpSceneAuthentication: + processing = totp_scene_authenticate_handle_event(event, plugin_state); + break; + case TotpSceneAddNewToken: + processing = totp_scene_add_new_token_handle_event(event, plugin_state); + break; + case TotpSceneTokenMenu: + processing = totp_scene_token_menu_handle_event(event, plugin_state); + break; + } + + return processing; +} diff --git a/applications/plugins/totp/scenes/scene_director.h b/applications/plugins/totp/scenes/scene_director.h new file mode 100644 index 000000000..962b7e9a7 --- /dev/null +++ b/applications/plugins/totp/scenes/scene_director.h @@ -0,0 +1,16 @@ +#ifndef _SCENE_DIRECTOR_H_ +#define _SCENE_DIRECTOR_H_ + +#include +#include "../types/plugin_state.h" +#include "../types/plugin_event.h" +#include "totp_scenes_enum.h" + +void totp_scene_director_activate_scene(PluginState* const plugin_state, Scene scene, const void* context); +void totp_scene_director_deactivate_active_scene(PluginState* const plugin_state); +void totp_scene_director_init_scenes(PluginState* const plugin_state); +void totp_scene_director_render(Canvas* const canvas, PluginState* const plugin_state); +void totp_scene_director_dispose(PluginState* const plugin_state); +bool totp_scene_director_handle_event(PluginEvent* const event, PluginState* const plugin_state); + +#endif diff --git a/applications/plugins/totp/scenes/token_menu/totp_scene_token_menu.c b/applications/plugins/totp/scenes/token_menu/totp_scene_token_menu.c new file mode 100644 index 000000000..8f35dfcf7 --- /dev/null +++ b/applications/plugins/totp/scenes/token_menu/totp_scene_token_menu.c @@ -0,0 +1,115 @@ +#include "totp_scene_token_menu.h" +#include +#include +#include "../../lib/ui/ui_controls.h" +#include "../../lib/ui/constants.h" +#include "../scene_director.h" +#include "../../lib/config/config.h" +#include "../../lib/list/list.h" +#include "../../types/token_info.h" +#include "../generate_token/totp_scene_generate_token.h" +#include "../add_new_token/totp_scene_add_new_token.h" + +typedef enum { + AddNewToken, + DeleteToken +} Control; + +typedef struct { + Control selected_control; + uint8_t current_token_index; +} SceneState; + +void totp_scene_token_menu_init(PluginState* plugin_state) { + UNUSED(plugin_state); +} + +void totp_scene_token_menu_activate(PluginState* plugin_state, const TokenMenuSceneContext* context) { + SceneState* scene_state = malloc(sizeof(SceneState)); + plugin_state->current_scene_state = scene_state; + scene_state->current_token_index = context->current_token_index; +} + +void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_state) { + SceneState* scene_state = (SceneState *)plugin_state->current_scene_state; + ui_control_button_render(canvas, SCREEN_WIDTH_CENTER - 36, 5, 72, 21, "Add new token", scene_state->selected_control == AddNewToken); + ui_control_button_render(canvas, SCREEN_WIDTH_CENTER - 36, 39, 72, 21, "Delete token", scene_state->selected_control == DeleteToken); +} + +bool totp_scene_token_menu_handle_event(PluginEvent* const event, PluginState* plugin_state) { + if (event->type == EventTypeKey) { + SceneState* scene_state = (SceneState *)plugin_state->current_scene_state; + if(event->input.type == InputTypePress) { + switch(event->input.key) { + case InputKeyUp: + if (scene_state->selected_control > AddNewToken) { + scene_state->selected_control--; + } + break; + case InputKeyDown: + if (scene_state->selected_control < DeleteToken) { + scene_state->selected_control++; + } + break; + case InputKeyRight: + break; + case InputKeyLeft: + break; + case InputKeyOk: + switch (scene_state->selected_control) { + case AddNewToken: { + TokenAddEditSceneContext add_new_token_scene_context = { .current_token_index = scene_state->current_token_index }; + totp_scene_director_activate_scene(plugin_state, TotpSceneAddNewToken, &add_new_token_scene_context); + break; + } + case DeleteToken: { + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + 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(dialogs, message); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + if (dialog_result == DialogMessageButtonRight) { + uint8_t i = 0; + + ListNode* list_node = plugin_state->tokens_list; + while (i < scene_state->current_token_index && list_node->next != NULL) { + list_node = list_node->next; + i++; + } + + TokenInfo* tokenInfo = list_node->data; + token_info_free(tokenInfo); + plugin_state->tokens_list = list_remove(plugin_state->tokens_list, list_node); + plugin_state->tokens_count--; + + totp_full_save_config_file(plugin_state); + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } + break; + } + } + break; + case InputKeyBack: { + GenerateTokenSceneContext generate_scene_context = { .current_token_index = scene_state->current_token_index }; + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, &generate_scene_context); + break; + } + } + } + } + return true; +} + +void totp_scene_token_menu_deactivate(PluginState* plugin_state) { + if (plugin_state->current_scene_state == NULL) return; + + free(plugin_state->current_scene_state); + plugin_state->current_scene_state = NULL; +} + +void totp_scene_token_menu_free(PluginState* plugin_state) { + UNUSED(plugin_state); +} diff --git a/applications/plugins/totp/scenes/token_menu/totp_scene_token_menu.h b/applications/plugins/totp/scenes/token_menu/totp_scene_token_menu.h new file mode 100644 index 000000000..a73b5e8a3 --- /dev/null +++ b/applications/plugins/totp/scenes/token_menu/totp_scene_token_menu.h @@ -0,0 +1,21 @@ +#ifndef _TOTP_SCENE_TOKEN_MENU_H_ +#define _TOTP_SCENE_TOKEN_MENU_H_ + +#include +#include +#include +#include "../../types/plugin_state.h" +#include "../../types/plugin_event.h" + +typedef struct { + uint8_t current_token_index; +} TokenMenuSceneContext; + +void totp_scene_token_menu_init(PluginState* plugin_state); +void totp_scene_token_menu_activate(PluginState* plugin_state, const TokenMenuSceneContext* context); +void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_state); +bool totp_scene_token_menu_handle_event(PluginEvent* const event, PluginState* plugin_state); +void totp_scene_token_menu_deactivate(PluginState* plugin_state); +void totp_scene_token_menu_free(PluginState* plugin_state); + +#endif diff --git a/applications/plugins/totp/scenes/totp_scenes_enum.h b/applications/plugins/totp/scenes/totp_scenes_enum.h new file mode 100644 index 000000000..3e45992e5 --- /dev/null +++ b/applications/plugins/totp/scenes/totp_scenes_enum.h @@ -0,0 +1,9 @@ +#ifndef _TOTP_SCENES_ENUM_H_ +#define _TOTP_SCENES_ENUM_H_ +typedef enum { + TotpSceneAuthentication, + TotpSceneGenerateToken, + TotpSceneAddNewToken, + TotpSceneTokenMenu +} Scene; +#endif diff --git a/applications/plugins/totp/totp_10px.png b/applications/plugins/totp/totp_10px.png new file mode 100644 index 000000000..70ed56d98 Binary files /dev/null and b/applications/plugins/totp/totp_10px.png differ diff --git a/applications/plugins/totp/totp_app.c b/applications/plugins/totp/totp_app.c new file mode 100644 index 000000000..b55acefeb --- /dev/null +++ b/applications/plugins/totp/totp_app.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "lib/base32/base32.h" +#include "lib/list/list.h" +#include "lib/config/config.h" +#include "types/plugin_state.h" +#include "types/token_info.h" +#include "types/plugin_event.h" +#include "types/event_type.h" +#include "types/common.h" +#include "scenes/scene_director.h" + +#define IDLE_TIMEOUT 60000 + +static void render_callback(Canvas* const canvas, void* ctx) { + PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25); + if (plugin_state != NULL && !plugin_state->changing_scene) { + totp_scene_director_render(canvas, plugin_state); + } + + release_mutex((ValueMutex*)ctx, plugin_state); +} + +static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + PluginEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void totp_state_init(PluginState* const plugin_state) { + plugin_state->gui = furi_record_open(RECORD_GUI); + plugin_state->notification = furi_record_open(RECORD_NOTIFICATION); + 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); + + ListNode* node = plugin_state->tokens_list; + ListNode* tmp; + while (node != NULL) { + tmp = node->next; + TokenInfo* tokenInfo = (TokenInfo*)node->data; + token_info_free(tokenInfo); + free(node); + node = tmp; + } + + if (plugin_state->crypto_verify_data != NULL) { + free(plugin_state->crypto_verify_data); + } + free(plugin_state); +} + +int32_t totp_app() { + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + PluginState* plugin_state = malloc(sizeof(PluginState)); + + totp_state_init(plugin_state); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) { + FURI_LOG_E(LOGGING_TAG, "Cannot create mutex\r\n"); + dispose_plugin_state(plugin_state); + return 255; + } + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, &state_mutex); + view_port_input_callback_set(view_port, input_callback, event_queue); + + // Open GUI and register view_port + gui_add_view_port(plugin_state->gui, view_port, GuiLayerFullscreen); + + PluginEvent event; + bool processing = true; + uint32_t last_user_interaction_time = furi_get_tick(); + while(processing) { + if (plugin_state->changing_scene) continue; + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + + PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + if (event.type == EventTypeKey) { + last_user_interaction_time = furi_get_tick(); + } + + processing = totp_scene_director_handle_event(&event, plugin_state); + } else if (plugin_state->current_scene != TotpSceneAuthentication && furi_get_tick() - last_user_interaction_time > IDLE_TIMEOUT) { + totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL); + } + + view_port_update(view_port); + release_mutex(&state_mutex, plugin_state); + } + + view_port_enabled_set(view_port, false); + gui_remove_view_port(plugin_state->gui, view_port); + view_port_free(view_port); + furi_message_queue_free(event_queue); + delete_mutex(&state_mutex); + dispose_plugin_state(plugin_state); + return 0; +} diff --git a/applications/plugins/totp/totp_app_cli.c b/applications/plugins/totp/totp_app_cli.c new file mode 100644 index 000000000..ddbecb950 --- /dev/null +++ b/applications/plugins/totp/totp_app_cli.c @@ -0,0 +1,32 @@ +#include +#include +#include + +void totp_cli_print_usage() { + printf("Usage:\r\n"); + printf("totp \r\n"); + printf("Cmd list:\r\n"); + printf("\tadd \t - Add new TOTP secret\r\n"); + printf("\tremove \t - Remove TOTP token\r\n"); + printf("\reset\t - Reset app to default (reset PIN and removes all tokens)\r\n"); +}; + +static void totp_cli(Cli* cli, string_t args, void* context) { + UNUSED(cli); + UNUSED(args); + UNUSED(context); + totp_cli_print_usage(); + // TODO: implement add\remove\reset +} + +void totp_on_system_start() { +#ifdef SRV_CLI + Cli* cli = furi_record_open(RECORD_CLI); + + cli_add_command(cli, "totp", CliCommandFlagDefault, totp_cli, NULL); + + furi_record_close(RECORD_CLI); +#else + UNUSED(totp_cli); +#endif +} diff --git a/applications/plugins/totp/types/common.h b/applications/plugins/totp/types/common.h new file mode 100644 index 000000000..1fb6b5735 --- /dev/null +++ b/applications/plugins/totp/types/common.h @@ -0,0 +1,7 @@ +#ifndef _TOTP_COMMON_TYPES_H_ +#define _TOTP_COMMON_TYPES_H_ + +#define LOGGING_TAG "TOTP APP" +#define CRYPTO_KEY_SLOT 2 + +#endif diff --git a/applications/plugins/totp/types/event_type.h b/applications/plugins/totp/types/event_type.h new file mode 100644 index 000000000..f828e29bd --- /dev/null +++ b/applications/plugins/totp/types/event_type.h @@ -0,0 +1,11 @@ +#ifndef _TOTP_EVENT_TYPE_H_ +#define _TOTP_EVENT_TYPE_H_ + +#include + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +#endif diff --git a/applications/plugins/totp/types/plugin_event.h b/applications/plugins/totp/types/plugin_event.h new file mode 100644 index 000000000..68c5bef7e --- /dev/null +++ b/applications/plugins/totp/types/plugin_event.h @@ -0,0 +1,13 @@ +#ifndef _TOTP_PLUGIN_EVENT_H_ +#define _TOTP_PLUGIN_EVENT_H_ + +#include +#include +#include "event_type.h" + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +#endif diff --git a/applications/plugins/totp/types/plugin_state.h b/applications/plugins/totp/types/plugin_state.h new file mode 100644 index 000000000..52e757fa0 --- /dev/null +++ b/applications/plugins/totp/types/plugin_state.h @@ -0,0 +1,27 @@ +#ifndef _TOTP_PLUGIN_STATE_H_ +#define _TOTP_PLUGIN_STATE_H_ + +#include +#include +#include "../lib/list/list.h" +#include "../scenes/totp_scenes_enum.h" + +typedef struct { + Scene current_scene; + void* current_scene_state; + bool changing_scene; + NotificationApp* notification; + 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[16]; + uint8_t base_iv[16]; +} PluginState; + +#endif diff --git a/applications/plugins/totp/types/token_info.c b/applications/plugins/totp/types/token_info.c new file mode 100644 index 000000000..270d7be42 --- /dev/null +++ b/applications/plugins/totp/types/token_info.c @@ -0,0 +1,38 @@ +#include +#include +#include "token_info.h" +#include "stdlib.h" +#include "common.h" +#include "../lib/base32/base32.h" + +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); +} diff --git a/applications/plugins/totp/types/token_info.h b/applications/plugins/totp/types/token_info.h new file mode 100644 index 000000000..6923b4a35 --- /dev/null +++ b/applications/plugins/totp/types/token_info.h @@ -0,0 +1,15 @@ +#ifndef _TOTP_TOKEN_INFO_H_ +#define _TOTP_TOKEN_INFO_H_ + +#include + +typedef struct { + uint8_t* token; + uint8_t token_length; + char* name; +} TokenInfo; + +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); + +#endif