token_info_iterator.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. #include "token_info_iterator.h"
  2. #include <flipper_format/flipper_format_i.h>
  3. #include <flipper_format/flipper_format_stream.h>
  4. #include <toolbox/stream/file_stream.h>
  5. #include "../../types/common.h"
  6. #include "../../types/crypto_settings.h"
  7. #define CONFIG_FILE_PART_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/totp.conf.part"
  8. #define STREAM_COPY_BUFFER_SIZE 128
  9. struct TokenInfoIteratorContext {
  10. size_t total_count;
  11. size_t current_index;
  12. size_t last_seek_offset;
  13. size_t last_seek_index;
  14. TokenInfo* current_token;
  15. FlipperFormat* config_file;
  16. CryptoSettings* crypto_settings;
  17. Storage* storage;
  18. };
  19. static bool
  20. flipper_format_seek_to_siblinig_token_start(Stream* stream, StreamDirection direction) {
  21. char buffer[sizeof(TOTP_CONFIG_KEY_TOKEN_NAME) + 1];
  22. bool found = false;
  23. while(!found) {
  24. if(!stream_seek_to_char(stream, '\n', direction)) {
  25. break;
  26. }
  27. size_t buffer_read_size;
  28. if((buffer_read_size = stream_read(stream, (uint8_t*)&buffer[0], sizeof(buffer))) == 0) {
  29. break;
  30. }
  31. if(!stream_seek(stream, -(int32_t)buffer_read_size, StreamOffsetFromCurrent)) {
  32. break;
  33. }
  34. if(strncmp(buffer, "\n" TOTP_CONFIG_KEY_TOKEN_NAME ":", sizeof(buffer)) == 0) {
  35. found = true;
  36. }
  37. }
  38. return found;
  39. }
  40. static bool seek_to_token(size_t token_index, TokenInfoIteratorContext* context) {
  41. furi_check(context != NULL && context->config_file != NULL);
  42. if(token_index >= context->total_count) {
  43. return false;
  44. }
  45. Stream* stream = flipper_format_get_raw_stream(context->config_file);
  46. long token_index_diff = (long)token_index - (long)context->last_seek_index;
  47. size_t token_index_diff_weight = (size_t)labs(token_index_diff);
  48. StreamDirection direction = token_index_diff >= 0 ? StreamDirectionForward :
  49. StreamDirectionBackward;
  50. if(token_index_diff_weight > token_index || context->last_seek_offset == 0) {
  51. context->last_seek_offset = 0;
  52. context->last_seek_index = 0;
  53. token_index_diff = token_index + 1;
  54. direction = StreamDirectionForward;
  55. } else if(token_index_diff_weight > (context->total_count - token_index - 1)) {
  56. context->last_seek_offset = stream_size(stream);
  57. context->last_seek_index = context->total_count - 1;
  58. token_index_diff = -(long)(context->total_count - token_index);
  59. direction = StreamDirectionBackward;
  60. }
  61. if(!stream_seek(stream, context->last_seek_offset, StreamOffsetFromStart)) {
  62. return false;
  63. }
  64. if(token_index_diff != 0) {
  65. long i = 0;
  66. long i_inc = token_index_diff >= 0 ? 1 : -1;
  67. do {
  68. if(!flipper_format_seek_to_siblinig_token_start(stream, direction)) {
  69. break;
  70. }
  71. i += i_inc;
  72. } while((i_inc > 0 && i < token_index_diff) || (i_inc < 0 && i > token_index_diff));
  73. if((i_inc > 0 && i < token_index_diff) || (i_inc < 0 && i > token_index_diff)) {
  74. context->last_seek_offset = 0;
  75. FURI_LOG_D(LOGGING_TAG, "Was not able to move");
  76. return false;
  77. }
  78. context->last_seek_offset = stream_tell(stream);
  79. context->last_seek_index = token_index;
  80. }
  81. return true;
  82. }
  83. static bool stream_insert_stream(Stream* dst, Stream* src) {
  84. uint8_t buffer[STREAM_COPY_BUFFER_SIZE];
  85. size_t buffer_read_size;
  86. while((buffer_read_size = stream_read(src, buffer, sizeof(buffer))) != 0) {
  87. if(!stream_insert(dst, buffer, buffer_read_size)) {
  88. return false;
  89. }
  90. }
  91. return true;
  92. }
  93. static bool ensure_stream_ends_with_lf(Stream* stream) {
  94. uint8_t last_char;
  95. size_t original_pos = stream_tell(stream);
  96. if(!stream_seek(stream, -1, StreamOffsetFromEnd) || stream_read(stream, &last_char, 1) < 1) {
  97. return false;
  98. }
  99. const uint8_t lf = '\n';
  100. if(last_char != lf && !stream_write(stream, &lf, 1)) {
  101. return false;
  102. }
  103. if(!stream_seek(stream, original_pos, StreamOffsetFromStart)) {
  104. return false;
  105. }
  106. return true;
  107. }
  108. static bool
  109. totp_token_info_iterator_save_current_token_info_changes(TokenInfoIteratorContext* context) {
  110. bool is_new_token = context->current_index >= context->total_count;
  111. Stream* stream = flipper_format_get_raw_stream(context->config_file);
  112. if(is_new_token) {
  113. if(!ensure_stream_ends_with_lf(stream) ||
  114. !flipper_format_seek_to_end(context->config_file)) {
  115. return false;
  116. }
  117. } else {
  118. if(!seek_to_token(context->current_index, context)) {
  119. return false;
  120. }
  121. }
  122. size_t offset_start = stream_tell(stream);
  123. size_t offset_end;
  124. if(is_new_token) {
  125. offset_end = offset_start;
  126. } else if(context->current_index + 1 >= context->total_count) {
  127. offset_end = stream_size(stream);
  128. } else if(seek_to_token(context->current_index + 1, context)) {
  129. offset_end = stream_tell(stream);
  130. } else {
  131. return false;
  132. }
  133. FlipperFormat* temp_ff = flipper_format_file_alloc(context->storage);
  134. if(!flipper_format_file_open_always(temp_ff, CONFIG_FILE_PART_FILE_PATH)) {
  135. flipper_format_free(temp_ff);
  136. return false;
  137. }
  138. TokenInfo* token_info = context->current_token;
  139. bool result = false;
  140. do {
  141. if(!flipper_format_write_string(temp_ff, TOTP_CONFIG_KEY_TOKEN_NAME, token_info->name)) {
  142. break;
  143. }
  144. if(!flipper_format_write_hex(
  145. temp_ff,
  146. TOTP_CONFIG_KEY_TOKEN_SECRET,
  147. token_info->token,
  148. token_info->token_length)) {
  149. break;
  150. }
  151. uint32_t tmp_uint32 = token_info->algo;
  152. if(!flipper_format_write_uint32(temp_ff, TOTP_CONFIG_KEY_TOKEN_ALGO, &tmp_uint32, 1)) {
  153. break;
  154. }
  155. tmp_uint32 = token_info->digits;
  156. if(!flipper_format_write_uint32(temp_ff, TOTP_CONFIG_KEY_TOKEN_DIGITS, &tmp_uint32, 1)) {
  157. break;
  158. }
  159. tmp_uint32 = token_info->duration;
  160. if(!flipper_format_write_uint32(temp_ff, TOTP_CONFIG_KEY_TOKEN_DURATION, &tmp_uint32, 1)) {
  161. break;
  162. }
  163. tmp_uint32 = token_info->automation_features;
  164. if(!flipper_format_write_uint32(
  165. temp_ff, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, &tmp_uint32, 1)) {
  166. break;
  167. }
  168. Stream* temp_stream = flipper_format_get_raw_stream(temp_ff);
  169. if(!stream_rewind(temp_stream)) {
  170. break;
  171. }
  172. if(!stream_seek(stream, offset_start, StreamOffsetFromStart)) {
  173. break;
  174. }
  175. if(offset_end != offset_start && !stream_delete(stream, offset_end - offset_start)) {
  176. break;
  177. }
  178. if(!is_new_token && !stream_write_char(stream, '\n')) {
  179. break;
  180. }
  181. if(!stream_insert_stream(stream, temp_stream)) {
  182. break;
  183. }
  184. if(is_new_token) {
  185. context->total_count++;
  186. }
  187. result = true;
  188. } while(false);
  189. flipper_format_free(temp_ff);
  190. storage_common_remove(context->storage, CONFIG_FILE_PART_FILE_PATH);
  191. stream_seek(stream, offset_start, StreamOffsetFromStart);
  192. context->last_seek_offset = offset_start;
  193. context->last_seek_index = context->current_index;
  194. return result;
  195. }
  196. TokenInfoIteratorContext* totp_token_info_iterator_alloc(
  197. Storage* storage,
  198. FlipperFormat* config_file,
  199. CryptoSettings* crypto_settings) {
  200. Stream* stream = flipper_format_get_raw_stream(config_file);
  201. stream_rewind(stream);
  202. size_t tokens_count = 0;
  203. while(true) {
  204. if(!flipper_format_seek_to_siblinig_token_start(stream, StreamDirectionForward)) {
  205. break;
  206. }
  207. tokens_count++;
  208. }
  209. TokenInfoIteratorContext* context = malloc(sizeof(TokenInfoIteratorContext));
  210. furi_check(context != NULL);
  211. context->total_count = tokens_count;
  212. context->current_token = token_info_alloc();
  213. context->config_file = config_file;
  214. context->crypto_settings = crypto_settings;
  215. context->storage = storage;
  216. return context;
  217. }
  218. void totp_token_info_iterator_free(TokenInfoIteratorContext* context) {
  219. if(context == NULL) return;
  220. token_info_free(context->current_token);
  221. free(context);
  222. }
  223. bool totp_token_info_iterator_remove_current_token_info(TokenInfoIteratorContext* context) {
  224. if(!seek_to_token(context->current_index, context)) {
  225. return false;
  226. }
  227. Stream* stream = flipper_format_get_raw_stream(context->config_file);
  228. size_t begin_offset = stream_tell(stream);
  229. size_t end_offset;
  230. if(!ensure_stream_ends_with_lf(stream)) {
  231. return false;
  232. }
  233. if(context->current_index >= context->total_count - 1) {
  234. end_offset = stream_size(stream) - 1;
  235. } else if(seek_to_token(context->current_index + 1, context)) {
  236. end_offset = stream_tell(stream);
  237. } else {
  238. return false;
  239. }
  240. if(!stream_seek(stream, begin_offset, StreamOffsetFromStart) ||
  241. !stream_delete(stream, end_offset - begin_offset)) {
  242. return false;
  243. }
  244. context->total_count--;
  245. if(context->current_index >= context->total_count) {
  246. context->current_index = context->total_count - 1;
  247. }
  248. return true;
  249. }
  250. bool totp_token_info_iterator_move_current_token_info(
  251. TokenInfoIteratorContext* context,
  252. size_t new_index) {
  253. if(context->current_index == new_index) return true;
  254. Stream* stream = flipper_format_get_raw_stream(context->config_file);
  255. if(!ensure_stream_ends_with_lf(stream)) {
  256. return false;
  257. }
  258. if(!seek_to_token(context->current_index, context)) {
  259. return false;
  260. }
  261. size_t begin_offset = stream_tell(stream);
  262. size_t end_offset;
  263. if(context->current_index >= context->total_count - 1) {
  264. end_offset = stream_size(stream) - 1;
  265. } else if(seek_to_token(context->current_index + 1, context)) {
  266. end_offset = stream_tell(stream);
  267. } else {
  268. return false;
  269. }
  270. Stream* temp_stream = file_stream_alloc(context->storage);
  271. if(!file_stream_open(
  272. temp_stream, CONFIG_FILE_PART_FILE_PATH, FSAM_READ_WRITE, FSOM_CREATE_ALWAYS)) {
  273. stream_free(temp_stream);
  274. return false;
  275. }
  276. size_t moving_size = end_offset - begin_offset;
  277. bool result = false;
  278. do {
  279. if(!stream_seek(stream, begin_offset, StreamOffsetFromStart)) {
  280. break;
  281. }
  282. if(stream_copy(stream, temp_stream, moving_size) < moving_size) {
  283. break;
  284. }
  285. if(!stream_rewind(temp_stream)) {
  286. break;
  287. }
  288. if(!stream_seek(stream, begin_offset, StreamOffsetFromStart)) {
  289. break;
  290. }
  291. if(!stream_delete(stream, moving_size)) {
  292. break;
  293. }
  294. context->last_seek_offset = 0;
  295. context->last_seek_index = 0;
  296. if(new_index >= context->total_count - 1) {
  297. if(!stream_seek(stream, stream_size(stream) - 1, StreamOffsetFromStart)) {
  298. break;
  299. }
  300. } else if(!seek_to_token(new_index, context)) {
  301. break;
  302. }
  303. result = stream_insert_stream(stream, temp_stream);
  304. } while(false);
  305. stream_free(temp_stream);
  306. storage_common_remove(context->storage, CONFIG_FILE_PART_FILE_PATH);
  307. context->last_seek_offset = 0;
  308. context->last_seek_index = 0;
  309. return result;
  310. }
  311. TotpIteratorUpdateTokenResult totp_token_info_iterator_update_current_token(
  312. TokenInfoIteratorContext* context,
  313. TOTP_ITERATOR_UPDATE_TOKEN_ACTION update,
  314. const void* update_context) {
  315. TotpIteratorUpdateTokenResult result = update(context->current_token, update_context);
  316. if(result == TotpIteratorUpdateTokenResultSuccess) {
  317. if(!totp_token_info_iterator_save_current_token_info_changes(context)) {
  318. result = TotpIteratorUpdateTokenResultFileUpdateFailed;
  319. }
  320. return result;
  321. }
  322. totp_token_info_iterator_go_to(context, context->current_index);
  323. return result;
  324. }
  325. TotpIteratorUpdateTokenResult totp_token_info_iterator_add_new_token(
  326. TokenInfoIteratorContext* context,
  327. TOTP_ITERATOR_UPDATE_TOKEN_ACTION update,
  328. const void* update_context) {
  329. size_t previous_index = context->current_index;
  330. context->current_index = context->total_count;
  331. token_info_set_defaults(context->current_token);
  332. TotpIteratorUpdateTokenResult result = update(context->current_token, update_context);
  333. if(result == TotpIteratorUpdateTokenResultSuccess &&
  334. !totp_token_info_iterator_save_current_token_info_changes(context)) {
  335. result = TotpIteratorUpdateTokenResultFileUpdateFailed;
  336. }
  337. if(result != TotpIteratorUpdateTokenResultSuccess) {
  338. totp_token_info_iterator_go_to(context, previous_index);
  339. }
  340. return result;
  341. }
  342. bool totp_token_info_iterator_go_to(TokenInfoIteratorContext* context, size_t token_index) {
  343. furi_check(context != NULL);
  344. context->current_index = token_index;
  345. if(!seek_to_token(context->current_index, context)) {
  346. return false;
  347. }
  348. Stream* stream = flipper_format_get_raw_stream(context->config_file);
  349. size_t original_offset = stream_tell(stream);
  350. if(!flipper_format_read_string(
  351. context->config_file, TOTP_CONFIG_KEY_TOKEN_NAME, context->current_token->name)) {
  352. stream_seek(stream, original_offset, StreamOffsetFromStart);
  353. return false;
  354. }
  355. uint32_t secret_bytes_count;
  356. if(!flipper_format_get_value_count(
  357. context->config_file, TOTP_CONFIG_KEY_TOKEN_SECRET, &secret_bytes_count)) {
  358. secret_bytes_count = 0;
  359. }
  360. TokenInfo* tokenInfo = context->current_token;
  361. bool token_update_needed = false;
  362. if(tokenInfo->token != NULL) {
  363. free(tokenInfo->token);
  364. tokenInfo->token_length = 0;
  365. }
  366. if(secret_bytes_count == 1) { // Plain secret key
  367. FuriString* temp_str = furi_string_alloc();
  368. if(flipper_format_read_string(
  369. context->config_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str)) {
  370. if(token_info_set_secret(
  371. tokenInfo,
  372. furi_string_get_cstr(temp_str),
  373. furi_string_size(temp_str),
  374. PlainTokenSecretEncodingBase32,
  375. context->crypto_settings)) {
  376. FURI_LOG_W(
  377. LOGGING_TAG,
  378. "Token \"%s\" has plain secret",
  379. furi_string_get_cstr(tokenInfo->name));
  380. token_update_needed = true;
  381. } else {
  382. tokenInfo->token = NULL;
  383. tokenInfo->token_length = 0;
  384. FURI_LOG_W(
  385. LOGGING_TAG,
  386. "Token \"%s\" has invalid secret",
  387. furi_string_get_cstr(tokenInfo->name));
  388. }
  389. } else {
  390. tokenInfo->token = NULL;
  391. tokenInfo->token_length = 0;
  392. }
  393. furi_string_free(temp_str);
  394. } else { // encrypted
  395. tokenInfo->token_length = secret_bytes_count;
  396. if(secret_bytes_count > 0) {
  397. tokenInfo->token = malloc(tokenInfo->token_length);
  398. furi_check(tokenInfo->token != NULL);
  399. if(!flipper_format_read_hex(
  400. context->config_file,
  401. TOTP_CONFIG_KEY_TOKEN_SECRET,
  402. tokenInfo->token,
  403. tokenInfo->token_length)) {
  404. free(tokenInfo->token);
  405. tokenInfo->token = NULL;
  406. tokenInfo->token_length = 0;
  407. }
  408. } else {
  409. tokenInfo->token = NULL;
  410. }
  411. }
  412. uint32_t temp_data32;
  413. if(!flipper_format_read_uint32(
  414. context->config_file, TOTP_CONFIG_KEY_TOKEN_ALGO, &temp_data32, 1) ||
  415. !token_info_set_algo_from_int(tokenInfo, temp_data32)) {
  416. tokenInfo->algo = TokenHashAlgoDefault;
  417. }
  418. if(!flipper_format_read_uint32(
  419. context->config_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &temp_data32, 1) ||
  420. !token_info_set_digits_from_int(tokenInfo, temp_data32)) {
  421. tokenInfo->digits = TokenDigitsCountSix;
  422. }
  423. if(!flipper_format_read_uint32(
  424. context->config_file, TOTP_CONFIG_KEY_TOKEN_DURATION, &temp_data32, 1) ||
  425. !token_info_set_duration_from_int(tokenInfo, temp_data32)) {
  426. tokenInfo->duration = TokenDurationDefault;
  427. }
  428. if(flipper_format_read_uint32(
  429. context->config_file, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, &temp_data32, 1)) {
  430. tokenInfo->automation_features = temp_data32;
  431. } else {
  432. tokenInfo->automation_features = TokenAutomationFeatureNone;
  433. }
  434. stream_seek(stream, original_offset, StreamOffsetFromStart);
  435. if(token_update_needed && !totp_token_info_iterator_save_current_token_info_changes(context)) {
  436. return false;
  437. }
  438. return true;
  439. }
  440. const TokenInfo*
  441. totp_token_info_iterator_get_current_token(const TokenInfoIteratorContext* context) {
  442. return context->current_token;
  443. }
  444. size_t totp_token_info_iterator_get_current_token_index(const TokenInfoIteratorContext* context) {
  445. return context->current_index;
  446. }
  447. size_t totp_token_info_iterator_get_total_count(const TokenInfoIteratorContext* context) {
  448. return context->total_count;
  449. }
  450. void totp_token_info_iterator_attach_to_config_file(
  451. TokenInfoIteratorContext* context,
  452. FlipperFormat* config_file) {
  453. context->config_file = config_file;
  454. }