generate_totp_code.c 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. #include "generate_totp_code.h"
  2. #include <furi/core/thread.h>
  3. #include <furi/core/check.h>
  4. #include "../../services/crypto/crypto_facade.h"
  5. #include "../../services/totp/totp.h"
  6. #include "../../services/convert/convert.h"
  7. #include <furi_hal_rtc.h>
  8. #include <memset_s.h>
  9. #define ONE_SEC_MS (1000)
  10. struct TotpGenerateCodeWorkerContext {
  11. char* code_buffer;
  12. FuriThread* thread;
  13. FuriMutex* code_buffer_sync;
  14. const TokenInfo* token_info;
  15. float timezone_offset;
  16. const CryptoSettings* crypto_settings;
  17. TOTP_NEW_CODE_GENERATED_HANDLER on_new_code_generated_handler;
  18. void* on_new_code_generated_handler_context;
  19. TOTP_CODE_LIFETIME_CHANGED_HANDLER on_code_lifetime_changed_handler;
  20. void* on_code_lifetime_changed_handler_context;
  21. };
  22. static const char STEAM_ALGO_ALPHABET[] = "23456789BCDFGHJKMNPQRTVWXY";
  23. static void
  24. int_token_to_str(uint64_t i_token_code, char* str, TokenDigitsCount len, TokenHashAlgo algo) {
  25. char* last_char = str + len;
  26. *last_char = '\0';
  27. if(i_token_code == OTP_ERROR) {
  28. memset(str, '-', len);
  29. } else {
  30. if(algo == TokenHashAlgoSteam) {
  31. char* s = str;
  32. for(uint8_t i = 0; i < len; i++, s++) {
  33. *s = STEAM_ALGO_ALPHABET[i_token_code % 26];
  34. i_token_code = i_token_code / 26;
  35. }
  36. } else {
  37. char* s = --last_char;
  38. for(int8_t i = len - 1; i >= 0; i--, s--) {
  39. *s = CONVERT_DIGIT_TO_CHAR(i_token_code % 10);
  40. i_token_code = i_token_code / 10;
  41. }
  42. }
  43. }
  44. }
  45. static TOTP_ALGO get_totp_algo_impl(TokenHashAlgo algo) {
  46. switch(algo) {
  47. case TokenHashAlgoSha1:
  48. case TokenHashAlgoSteam:
  49. return TOTP_ALGO_SHA1;
  50. case TokenHashAlgoSha256:
  51. return TOTP_ALGO_SHA256;
  52. case TokenHashAlgoSha512:
  53. return TOTP_ALGO_SHA512;
  54. default:
  55. break;
  56. }
  57. return NULL;
  58. }
  59. static void generate_totp_code(
  60. TotpGenerateCodeWorkerContext* context,
  61. const TokenInfo* token_info,
  62. uint32_t current_ts) {
  63. if(token_info->token != NULL && token_info->token_length > 0) {
  64. size_t key_length;
  65. uint8_t* key = totp_crypto_decrypt(
  66. token_info->token, token_info->token_length, context->crypto_settings, &key_length);
  67. uint64_t otp_code;
  68. size_t plain_key_length =
  69. token_info->token_plain_length > 0 ? token_info->token_plain_length : key_length;
  70. if(token_info->type == TokenTypeTOTP) {
  71. otp_code = totp_at(
  72. get_totp_algo_impl(token_info->algo),
  73. key,
  74. plain_key_length,
  75. current_ts,
  76. context->timezone_offset,
  77. token_info->duration);
  78. } else if(token_info->type == TokenTypeHOTP) {
  79. otp_code = hotp_at(
  80. get_totp_algo_impl(token_info->algo), key, plain_key_length, token_info->counter);
  81. } else {
  82. furi_crash("Unknown token type");
  83. }
  84. int_token_to_str(otp_code, context->code_buffer, token_info->digits, token_info->algo);
  85. memset_s(key, key_length, 0, key_length);
  86. free(key);
  87. } else {
  88. int_token_to_str(0, context->code_buffer, token_info->digits, token_info->algo);
  89. }
  90. }
  91. static int32_t totp_generate_worker_callback(void* context) {
  92. furi_check(context);
  93. TotpGenerateCodeWorkerContext* t_context = context;
  94. while(true) {
  95. uint32_t flags = furi_thread_flags_wait(
  96. TotpGenerateCodeWorkerEventStop | TotpGenerateCodeWorkerEventForceUpdate,
  97. FuriFlagWaitAny,
  98. ONE_SEC_MS);
  99. if(flags ==
  100. (uint32_t)
  101. FuriFlagErrorTimeout) { // If timeout, consider as no error, as we expect this and can handle gracefully
  102. flags = 0;
  103. }
  104. furi_check((flags & FuriFlagError) == 0); //-V562
  105. if(flags & TotpGenerateCodeWorkerEventStop) break;
  106. const TokenInfo* token_info = t_context->token_info;
  107. if(token_info == NULL) {
  108. continue;
  109. }
  110. const bool is_time_based = token_info->type == TokenTypeTOTP;
  111. uint32_t curr_ts = is_time_based ? furi_hal_rtc_get_timestamp() : 0;
  112. bool time_left = false;
  113. if(flags & TotpGenerateCodeWorkerEventForceUpdate ||
  114. (is_time_based && (time_left = (curr_ts % token_info->duration) == 0))) {
  115. if(furi_mutex_acquire(t_context->code_buffer_sync, FuriWaitForever) == FuriStatusOk) {
  116. generate_totp_code(t_context, token_info, curr_ts);
  117. if(is_time_based) {
  118. curr_ts = furi_hal_rtc_get_timestamp();
  119. }
  120. furi_mutex_release(t_context->code_buffer_sync);
  121. if(t_context->on_new_code_generated_handler != NULL) {
  122. (*(t_context->on_new_code_generated_handler))(
  123. time_left, t_context->on_new_code_generated_handler_context);
  124. }
  125. }
  126. }
  127. if(t_context->on_code_lifetime_changed_handler != NULL &&
  128. token_info->type == TokenTypeTOTP) {
  129. (*(t_context->on_code_lifetime_changed_handler))(
  130. (float)(token_info->duration - curr_ts % token_info->duration) /
  131. (float)token_info->duration,
  132. t_context->on_code_lifetime_changed_handler_context);
  133. }
  134. }
  135. return 0;
  136. }
  137. TotpGenerateCodeWorkerContext* totp_generate_code_worker_start(
  138. char* code_buffer,
  139. const TokenInfo* token_info,
  140. FuriMutex* code_buffer_sync,
  141. float timezone_offset,
  142. const CryptoSettings* crypto_settings) {
  143. TotpGenerateCodeWorkerContext* context = malloc(sizeof(TotpGenerateCodeWorkerContext));
  144. furi_check(context != NULL);
  145. context->code_buffer = code_buffer;
  146. context->token_info = token_info;
  147. context->code_buffer_sync = code_buffer_sync;
  148. context->timezone_offset = timezone_offset;
  149. context->crypto_settings = crypto_settings;
  150. context->thread = furi_thread_alloc();
  151. furi_thread_set_name(context->thread, "TOTPGenerateWorker");
  152. furi_thread_set_stack_size(context->thread, 2048);
  153. furi_thread_set_context(context->thread, context);
  154. furi_thread_set_callback(context->thread, totp_generate_worker_callback);
  155. furi_thread_start(context->thread);
  156. return context;
  157. }
  158. void totp_generate_code_worker_stop(TotpGenerateCodeWorkerContext* context) {
  159. furi_check(context != NULL);
  160. furi_thread_flags_set(furi_thread_get_id(context->thread), TotpGenerateCodeWorkerEventStop);
  161. furi_thread_join(context->thread);
  162. furi_thread_free(context->thread);
  163. free(context);
  164. }
  165. void totp_generate_code_worker_notify(
  166. TotpGenerateCodeWorkerContext* context,
  167. TotpGenerateCodeWorkerEvent event) {
  168. furi_check(context != NULL);
  169. furi_thread_flags_set(furi_thread_get_id(context->thread), event);
  170. }
  171. void totp_generate_code_worker_set_code_generated_handler(
  172. TotpGenerateCodeWorkerContext* context,
  173. TOTP_NEW_CODE_GENERATED_HANDLER on_new_code_generated_handler,
  174. void* on_new_code_generated_handler_context) {
  175. furi_check(context != NULL);
  176. context->on_new_code_generated_handler = on_new_code_generated_handler;
  177. context->on_new_code_generated_handler_context = on_new_code_generated_handler_context;
  178. }
  179. void totp_generate_code_worker_set_lifetime_changed_handler(
  180. TotpGenerateCodeWorkerContext* context,
  181. TOTP_CODE_LIFETIME_CHANGED_HANDLER on_code_lifetime_changed_handler,
  182. void* on_code_lifetime_changed_handler_context) {
  183. furi_check(context != NULL);
  184. context->on_code_lifetime_changed_handler = on_code_lifetime_changed_handler;
  185. context->on_code_lifetime_changed_handler_context = on_code_lifetime_changed_handler_context;
  186. }