From 8520daf28b16911a35d4226297b57b536f5ac27f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 12 Oct 2022 05:01:37 +0300 Subject: [PATCH] addding some great plugins to be preinstalled and running fbt format, and fixing TOTP printf format --- ReadMe.md | 5 + applications/plugins/arkanoid/arkanoid_game.c | 4 +- applications/plugins/metronome/README.md | 23 + .../plugins/metronome/application.fam | 14 + .../plugins/metronome/gui_extensions.c | 55 ++ .../plugins/metronome/gui_extensions.h | 3 + .../plugins/metronome/img/screenshot.png | Bin 0 -> 1978 bytes .../plugins/metronome/img/wave_left_4x14.png | Bin 0 -> 144 bytes .../plugins/metronome/img/wave_right_4x14.png | Bin 0 -> 132 bytes applications/plugins/metronome/metronome.c | 383 ++++++++++ .../plugins/metronome/metronome_icon.png | Bin 0 -> 170 bytes applications/plugins/minesweeper/LICENSE | 674 ++++++++++++++++++ applications/plugins/minesweeper/README.md | 19 + .../plugins/minesweeper/application.fam | 12 + applications/plugins/minesweeper/assets.h | 144 ++++ applications/plugins/minesweeper/assets/asset | 48 ++ .../plugins/minesweeper/assets/mockup.png | Bin 0 -> 400 bytes .../plugins/minesweeper/assets/tile_0.png | Bin 0 -> 130 bytes .../plugins/minesweeper/assets/tile_0.xbm | 4 + .../plugins/minesweeper/assets/tile_1.png | Bin 0 -> 152 bytes .../plugins/minesweeper/assets/tile_1.xbm | 4 + .../plugins/minesweeper/assets/tile_2.png | Bin 0 -> 164 bytes .../plugins/minesweeper/assets/tile_2.xbm | 4 + .../plugins/minesweeper/assets/tile_3.png | Bin 0 -> 166 bytes .../plugins/minesweeper/assets/tile_3.xbm | 4 + .../plugins/minesweeper/assets/tile_4.png | Bin 0 -> 164 bytes .../plugins/minesweeper/assets/tile_4.xbm | 4 + .../plugins/minesweeper/assets/tile_5.png | Bin 0 -> 167 bytes .../plugins/minesweeper/assets/tile_5.xbm | 4 + .../plugins/minesweeper/assets/tile_6.png | Bin 0 -> 164 bytes .../plugins/minesweeper/assets/tile_6.xbm | 4 + .../plugins/minesweeper/assets/tile_7.png | Bin 0 -> 165 bytes .../plugins/minesweeper/assets/tile_7.xbm | 4 + .../plugins/minesweeper/assets/tile_8.png | Bin 0 -> 161 bytes .../plugins/minesweeper/assets/tile_8.xbm | 4 + .../plugins/minesweeper/assets/tile_empty.png | Bin 0 -> 131 bytes .../plugins/minesweeper/assets/tile_flag.png | Bin 0 -> 170 bytes .../plugins/minesweeper/assets/tile_flag.xbm | 4 + .../plugins/minesweeper/assets/tile_mine.png | Bin 0 -> 152 bytes .../plugins/minesweeper/assets/tile_mine.xbm | 4 + .../minesweeper/assets/tile_uncleared.png | Bin 0 -> 127 bytes .../minesweeper/assets/tile_uncleared.xbm | 4 + .../plugins/minesweeper/img/screenshot.png | Bin 0 -> 1842 bytes .../plugins/minesweeper/minesweeper.c | 527 ++++++++++++++ .../plugins/minesweeper/minesweeper_icon.png | Bin 0 -> 161 bytes .../scenes/subbrute_scene_run_attack.c | 3 +- .../scenes/subbrute_scene_save_name.c | 9 +- applications/plugins/totp/.clang-format | 191 +++++ applications/plugins/totp/application.fam | 19 + .../scenes/add_new_token/totp_input_text.c | 75 ++ .../scenes/add_new_token/totp_input_text.h | 38 + .../add_new_token/totp_scene_add_new_token.c | 300 ++++++++ .../add_new_token/totp_scene_add_new_token.h | 20 + .../authenticate/totp_scene_authenticate.c | 203 ++++++ .../authenticate/totp_scene_authenticate.h | 14 + .../totp_scene_generate_token.c | 299 ++++++++ .../totp_scene_generate_token.h | 20 + .../plugins/totp/scenes/scene_director.c | 99 +++ .../plugins/totp/scenes/scene_director.h | 16 + .../scenes/token_menu/totp_scene_token_menu.c | 139 ++++ .../scenes/token_menu/totp_scene_token_menu.h | 20 + .../plugins/totp/scenes/totp_scenes_enum.h | 8 + .../plugins/totp/services/base32/base32.c | 94 +++ .../plugins/totp/services/base32/base32.h | 35 + .../plugins/totp/services/config/config.c | 381 ++++++++++ .../plugins/totp/services/config/config.h | 16 + .../plugins/totp/services/config/constants.h | 16 + .../migrations/config_migration_v1_to_v2.c | 46 ++ .../migrations/config_migration_v1_to_v2.h | 7 + .../plugins/totp/services/hmac/byteswap.c | 12 + .../plugins/totp/services/hmac/byteswap.h | 6 + .../plugins/totp/services/hmac/hmac_common.h | 64 ++ .../plugins/totp/services/hmac/hmac_sha1.c | 24 + .../plugins/totp/services/hmac/hmac_sha1.h | 11 + .../plugins/totp/services/hmac/hmac_sha256.c | 23 + .../plugins/totp/services/hmac/hmac_sha256.h | 11 + .../plugins/totp/services/hmac/hmac_sha512.c | 24 + .../plugins/totp/services/hmac/hmac_sha512.h | 11 + .../plugins/totp/services/hmac/memxor.c | 32 + .../plugins/totp/services/hmac/memxor.h | 28 + .../plugins/totp/services/hmac/sha1.c | 331 +++++++++ .../plugins/totp/services/hmac/sha1.h | 105 +++ .../plugins/totp/services/hmac/sha256.c | 385 ++++++++++ .../plugins/totp/services/hmac/sha256.h | 108 +++ .../plugins/totp/services/hmac/sha512.c | 430 +++++++++++ .../plugins/totp/services/hmac/sha512.h | 111 +++ applications/plugins/totp/services/hmac/u64.c | 20 + applications/plugins/totp/services/hmac/u64.h | 44 ++ .../plugins/totp/services/list/list.c | 72 ++ .../plugins/totp/services/list/list.h | 24 + .../services/timezone_utils/timezone_utils.c | 16 + .../services/timezone_utils/timezone_utils.h | 6 + .../plugins/totp/services/totp/totp.c | 150 ++++ .../plugins/totp/services/totp/totp.h | 53 ++ .../plugins/totp/services/ui/constants.h | 6 + applications/plugins/totp/services/ui/icons.h | 12 + .../plugins/totp/services/ui/ui_controls.c | 114 +++ .../plugins/totp/services/ui/ui_controls.h | 15 + applications/plugins/totp/totp_10px.png | Bin 0 -> 496 bytes applications/plugins/totp/totp_app.c | 126 ++++ applications/plugins/totp/types/common.h | 4 + applications/plugins/totp/types/event_type.h | 7 + .../plugins/totp/types/plugin_event.h | 10 + .../plugins/totp/types/plugin_state.h | 28 + applications/plugins/totp/types/token_info.c | 61 ++ applications/plugins/totp/types/token_info.h | 24 + applications/plugins/usbkeyboard/usb_hid.c | 26 +- 107 files changed, 6545 insertions(+), 16 deletions(-) create mode 100644 applications/plugins/metronome/README.md create mode 100644 applications/plugins/metronome/application.fam create mode 100644 applications/plugins/metronome/gui_extensions.c create mode 100644 applications/plugins/metronome/gui_extensions.h create mode 100644 applications/plugins/metronome/img/screenshot.png create mode 100644 applications/plugins/metronome/img/wave_left_4x14.png create mode 100644 applications/plugins/metronome/img/wave_right_4x14.png create mode 100644 applications/plugins/metronome/metronome.c create mode 100644 applications/plugins/metronome/metronome_icon.png create mode 100644 applications/plugins/minesweeper/LICENSE create mode 100644 applications/plugins/minesweeper/README.md create mode 100644 applications/plugins/minesweeper/application.fam create mode 100644 applications/plugins/minesweeper/assets.h create mode 100644 applications/plugins/minesweeper/assets/asset create mode 100644 applications/plugins/minesweeper/assets/mockup.png create mode 100644 applications/plugins/minesweeper/assets/tile_0.png create mode 100644 applications/plugins/minesweeper/assets/tile_0.xbm create mode 100644 applications/plugins/minesweeper/assets/tile_1.png create mode 100644 applications/plugins/minesweeper/assets/tile_1.xbm create mode 100644 applications/plugins/minesweeper/assets/tile_2.png create mode 100644 applications/plugins/minesweeper/assets/tile_2.xbm create mode 100644 applications/plugins/minesweeper/assets/tile_3.png create mode 100644 applications/plugins/minesweeper/assets/tile_3.xbm create mode 100644 applications/plugins/minesweeper/assets/tile_4.png create mode 100644 applications/plugins/minesweeper/assets/tile_4.xbm create mode 100644 applications/plugins/minesweeper/assets/tile_5.png create mode 100644 applications/plugins/minesweeper/assets/tile_5.xbm create mode 100644 applications/plugins/minesweeper/assets/tile_6.png create mode 100644 applications/plugins/minesweeper/assets/tile_6.xbm create mode 100644 applications/plugins/minesweeper/assets/tile_7.png create mode 100644 applications/plugins/minesweeper/assets/tile_7.xbm create mode 100644 applications/plugins/minesweeper/assets/tile_8.png create mode 100644 applications/plugins/minesweeper/assets/tile_8.xbm create mode 100644 applications/plugins/minesweeper/assets/tile_empty.png create mode 100644 applications/plugins/minesweeper/assets/tile_flag.png create mode 100644 applications/plugins/minesweeper/assets/tile_flag.xbm create mode 100644 applications/plugins/minesweeper/assets/tile_mine.png create mode 100644 applications/plugins/minesweeper/assets/tile_mine.xbm create mode 100644 applications/plugins/minesweeper/assets/tile_uncleared.png create mode 100644 applications/plugins/minesweeper/assets/tile_uncleared.xbm create mode 100644 applications/plugins/minesweeper/img/screenshot.png create mode 100644 applications/plugins/minesweeper/minesweeper.c create mode 100644 applications/plugins/minesweeper/minesweeper_icon.png create mode 100644 applications/plugins/totp/.clang-format create mode 100644 applications/plugins/totp/application.fam create mode 100644 applications/plugins/totp/scenes/add_new_token/totp_input_text.c create mode 100644 applications/plugins/totp/scenes/add_new_token/totp_input_text.h create mode 100644 applications/plugins/totp/scenes/add_new_token/totp_scene_add_new_token.c create mode 100644 applications/plugins/totp/scenes/add_new_token/totp_scene_add_new_token.h create mode 100644 applications/plugins/totp/scenes/authenticate/totp_scene_authenticate.c create mode 100644 applications/plugins/totp/scenes/authenticate/totp_scene_authenticate.h create mode 100644 applications/plugins/totp/scenes/generate_token/totp_scene_generate_token.c create mode 100644 applications/plugins/totp/scenes/generate_token/totp_scene_generate_token.h create mode 100644 applications/plugins/totp/scenes/scene_director.c create mode 100644 applications/plugins/totp/scenes/scene_director.h create mode 100644 applications/plugins/totp/scenes/token_menu/totp_scene_token_menu.c create mode 100644 applications/plugins/totp/scenes/token_menu/totp_scene_token_menu.h create mode 100644 applications/plugins/totp/scenes/totp_scenes_enum.h create mode 100644 applications/plugins/totp/services/base32/base32.c create mode 100644 applications/plugins/totp/services/base32/base32.h create mode 100644 applications/plugins/totp/services/config/config.c create mode 100644 applications/plugins/totp/services/config/config.h create mode 100644 applications/plugins/totp/services/config/constants.h create mode 100644 applications/plugins/totp/services/config/migrations/config_migration_v1_to_v2.c create mode 100644 applications/plugins/totp/services/config/migrations/config_migration_v1_to_v2.h create mode 100644 applications/plugins/totp/services/hmac/byteswap.c create mode 100644 applications/plugins/totp/services/hmac/byteswap.h create mode 100644 applications/plugins/totp/services/hmac/hmac_common.h create mode 100644 applications/plugins/totp/services/hmac/hmac_sha1.c create mode 100644 applications/plugins/totp/services/hmac/hmac_sha1.h create mode 100644 applications/plugins/totp/services/hmac/hmac_sha256.c create mode 100644 applications/plugins/totp/services/hmac/hmac_sha256.h create mode 100644 applications/plugins/totp/services/hmac/hmac_sha512.c create mode 100644 applications/plugins/totp/services/hmac/hmac_sha512.h create mode 100644 applications/plugins/totp/services/hmac/memxor.c create mode 100644 applications/plugins/totp/services/hmac/memxor.h create mode 100644 applications/plugins/totp/services/hmac/sha1.c create mode 100644 applications/plugins/totp/services/hmac/sha1.h create mode 100644 applications/plugins/totp/services/hmac/sha256.c create mode 100644 applications/plugins/totp/services/hmac/sha256.h create mode 100644 applications/plugins/totp/services/hmac/sha512.c create mode 100644 applications/plugins/totp/services/hmac/sha512.h create mode 100644 applications/plugins/totp/services/hmac/u64.c create mode 100644 applications/plugins/totp/services/hmac/u64.h create mode 100644 applications/plugins/totp/services/list/list.c create mode 100644 applications/plugins/totp/services/list/list.h create mode 100644 applications/plugins/totp/services/timezone_utils/timezone_utils.c create mode 100644 applications/plugins/totp/services/timezone_utils/timezone_utils.h create mode 100644 applications/plugins/totp/services/totp/totp.c create mode 100644 applications/plugins/totp/services/totp/totp.h create mode 100644 applications/plugins/totp/services/ui/constants.h create mode 100644 applications/plugins/totp/services/ui/icons.h create mode 100644 applications/plugins/totp/services/ui/ui_controls.c create mode 100644 applications/plugins/totp/services/ui/ui_controls.h create mode 100644 applications/plugins/totp/totp_10px.png create mode 100644 applications/plugins/totp/totp_app.c create mode 100644 applications/plugins/totp/types/common.h create mode 100644 applications/plugins/totp/types/event_type.h create mode 100644 applications/plugins/totp/types/plugin_event.h create mode 100644 applications/plugins/totp/types/plugin_state.h create mode 100644 applications/plugins/totp/types/token_info.c create mode 100644 applications/plugins/totp/types/token_info.h diff --git a/ReadMe.md b/ReadMe.md index c4464bff1..e5cb7eda5 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -80,6 +80,8 @@ Also check changelog in releases for latest updates! - Simple Clock (timer by GMMan / settings by kowalski7cc) [(Original by CompaqDisc)](https://gist.github.com/CompaqDisc/4e329c501bd03c1e801849b81f48ea61) - UniversalRF Remix / Sub-GHz Remote [(by ESurge)](https://github.com/ESurge/flipperzero-firmware-unirfremix)[(updated and all protocol support added by darmiel & xMasterX)](https://github.com/darmiel/flipper-playlist/tree/feat/unirf-protocols) - Spectrum Analyzer (with changes) [(by jolcese)](https://github.com/jolcese/flipperzero-firmware/tree/spectrum/applications/spectrum_analyzer) - [Ultra Narrow mode & scan channels non-consecutively](https://github.com/theY4Kman/flipperzero-firmware/commits?author=theY4Kman) +- Metronome [(by panki27)](https://github.com/panki27/Metronome) +- **TOTP (Authenticator)** [(by akopachov)](https://github.com/akopachov/flipper-zero_authenticator) Games: - DOOM (fixed) [(By p4nic4ttack)](https://github.com/p4nic4ttack/doom-flipper-zero/) @@ -88,6 +90,7 @@ Games: - Arkanoid (refactored by xMasterX) [(by gotnull)](https://github.com/gotnull/flipperzero-firmware-wPlugins) - Tic Tac Toe (refactored by xMasterX) [(by gotnull)](https://github.com/gotnull/flipperzero-firmware-wPlugins) - Tetris (with fixes) [(by jeffplang)](https://github.com/jeffplang/flipperzero-firmware/tree/tetris_game/applications/tetris_game) +- Minesweeper [(by panki27)](https://github.com/panki27/minesweeper) ### Other changes @@ -113,6 +116,8 @@ Games: ## [- Configure Sub-GHz Remote App](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemotePlugin.md) +## [- TOTP (Authenticator) config description](https://github.com/akopachov/flipper-zero_authenticator/blob/master/.github/conf-file_description.md) + ## [- Barcode Generator](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/BarcodeGenerator.md) ## [- Multi Converter](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/MultiConverter.md) diff --git a/applications/plugins/arkanoid/arkanoid_game.c b/applications/plugins/arkanoid/arkanoid_game.c index 70b8da256..91ae8e736 100644 --- a/applications/plugins/arkanoid/arkanoid_game.c +++ b/applications/plugins/arkanoid/arkanoid_game.c @@ -75,8 +75,8 @@ int rand_range(int min, int max) { void move_ball(Canvas* canvas, ArkanoidState* st) { st->tick++; - int current_speed = abs(st->speed-1 - MAX_SPEED); - if (st->tick % current_speed != 0 && st->tick % (current_speed + 1) != 0) { + int current_speed = abs(st->speed - 1 - MAX_SPEED); + if(st->tick % current_speed != 0 && st->tick % (current_speed + 1) != 0) { return; } diff --git a/applications/plugins/metronome/README.md b/applications/plugins/metronome/README.md new file mode 100644 index 000000000..4b6cd3122 --- /dev/null +++ b/applications/plugins/metronome/README.md @@ -0,0 +1,23 @@ +# Metronome + +[Original link](https://github.com/panki27/Metronome) + +A metronome for the [Flipper Zero](https://flipperzero.one/) device. Goes along perfectly with my [BPM tapper](https://github.com/panki27/bpm-tapper). + +![screenshot](img/screenshot.png) + +## Features + +- BPM adjustable, fine and coarse (hold pressed) +- Selectable amount of beats per bar +- Selectable note length +- First beat is pronounced +- Progress indicator +- LED flashes accordingly +- 3 different settings: Beep, Vibrate, Silent (push Down to change) + +## Compiling + +``` +./fbt firmware_metronome +``` diff --git a/applications/plugins/metronome/application.fam b/applications/plugins/metronome/application.fam new file mode 100644 index 000000000..c58cf881c --- /dev/null +++ b/applications/plugins/metronome/application.fam @@ -0,0 +1,14 @@ +App( + appid="Metronome", + name="Metronome", + apptype=FlipperAppType.EXTERNAL, + entry_point="metronome_app", + cdefines=["APP_METRONOME"], + requires=[ + "gui", + ], + fap_icon="metronome_icon.png", + fap_category="Music", + stack_size=2 * 1024, + order=20, +) diff --git a/applications/plugins/metronome/gui_extensions.c b/applications/plugins/metronome/gui_extensions.c new file mode 100644 index 000000000..ccad67930 --- /dev/null +++ b/applications/plugins/metronome/gui_extensions.c @@ -0,0 +1,55 @@ +#include +#include + +//lib can only do bottom left/right +void elements_button_top_left(Canvas* canvas, const char* str) { + const uint8_t button_height = 12; + const uint8_t vertical_offset = 3; + const uint8_t horizontal_offset = 3; + const uint8_t string_width = canvas_string_width(canvas, str); + const Icon* icon = &I_ButtonUp_7x4; + const uint8_t icon_h_offset = 3; + const uint8_t icon_width_with_offset = icon->width + icon_h_offset; + const uint8_t icon_v_offset = icon->height + vertical_offset; + const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; + + const uint8_t x = 0; + const uint8_t y = 0 + button_height; + + canvas_draw_box(canvas, x, y - button_height, button_width, button_height); + canvas_draw_line(canvas, x + button_width + 0, y - button_height, x + button_width + 0, y - 1); + canvas_draw_line(canvas, x + button_width + 1, y - button_height, x + button_width + 1, y - 2); + canvas_draw_line(canvas, x + button_width + 2, y - button_height, x + button_width + 2, y - 3); + + canvas_invert_color(canvas); + canvas_draw_icon(canvas, x + horizontal_offset, y - icon_v_offset, &I_ButtonUp_7x4); + canvas_draw_str( + canvas, x + horizontal_offset + icon_width_with_offset, y - vertical_offset, str); + canvas_invert_color(canvas); +} + +void elements_button_top_right(Canvas* canvas, const char* str) { + const uint8_t button_height = 12; + const uint8_t vertical_offset = 3; + const uint8_t horizontal_offset = 3; + const uint8_t string_width = canvas_string_width(canvas, str); + const Icon* icon = &I_ButtonUp_7x4; + const uint8_t icon_h_offset = 3; + const uint8_t icon_width_with_offset = icon->width + icon_h_offset; + const uint8_t icon_v_offset = icon->height + vertical_offset; + const uint8_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; + + const uint8_t x = canvas_width(canvas); + const uint8_t y = 0 + button_height; + + canvas_draw_box(canvas, x - button_width, y - button_height, button_width, button_height); + canvas_draw_line(canvas, x - button_width - 1, y - button_height, x - button_width - 1, y - 1); + canvas_draw_line(canvas, x - button_width - 2, y - button_height, x - button_width - 2, y - 2); + canvas_draw_line(canvas, x - button_width - 3, y - button_height, x - button_width - 3, y - 3); + + canvas_invert_color(canvas); + canvas_draw_str(canvas, x - button_width + horizontal_offset, y - vertical_offset, str); + canvas_draw_icon( + canvas, x - horizontal_offset - icon->width, y - icon_v_offset, &I_ButtonUp_7x4); + canvas_invert_color(canvas); +} diff --git a/applications/plugins/metronome/gui_extensions.h b/applications/plugins/metronome/gui_extensions.h new file mode 100644 index 000000000..97df1952c --- /dev/null +++ b/applications/plugins/metronome/gui_extensions.h @@ -0,0 +1,3 @@ +void elements_button_top_right(Canvas* canvas, const char* str); + +void elements_button_top_left(Canvas* canvas, const char* str); diff --git a/applications/plugins/metronome/img/screenshot.png b/applications/plugins/metronome/img/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..7b6916e81ac8e1984370d33397ac6664f57b5f6a GIT binary patch literal 1978 zcmbVNeNfV89Dfj<<<4w%mCJ-~)oST!>0D|8uCo_4JJST;>CPr6x~!O@5@4OHZKbU% z(tOEW(<~G%MDYdjT9bJSxP_UR=*`UWB1{o}28xqO+ppU{tv`PE{GR9g{(QdQ&+~2M zk%P`o8=U|E&Y_1wq5!a4`bB^h)+KBvZr9SX^3mGKT`MRo!`@6Z~^7qY?jg_o`{I%fhMoE?uum{0A5MZ^6Vg#Mh@q@`#mYhv`r}1;{EQ`BguAMzJ z={1VP15jmCBr8!}zt#nA?n;V7fPyv4g4!?hfFEb}y|9)5G7mGzG8T}HL`p=U`j2Ud zZ%#~rX{KWG6@z2Mdpra7q|%aZa_;b02$HJi+BX#!mr5IB78_D4>u2W_jdozgHURdm z1o@FMn0#&^p*Viv$M{^aVeIlAwVY`R?Sy`SsCDa>^A7`{V5b&w{i$4gEIejQadZozvGR4?%v8jj$ zi!2>N-YzVNr|6)778N2fK%1m2och@bY`zYb)AOTk-!)!6P@HhOf`PkrmAAoTDPOx~ z`2;p!TpK5pb!>00ar}OsA>U|1q(6jtC*}ciF@TH{BkR_R5M3cQyVqsE%hnJ;1Gv*? z?@q8FS4hFQG47v!-BMIvlt7+n^_NfRZ)RC8zi9uR21m3E6`Uq`2pPGvu9MoV!<=v| za~7$9!#g>RW)IX$R4mfCMzG*XP&H#Xh)P3%#>bn4cVo(5QM#d?-qYo8cr}9@LUclq zMdJkp_dg}`(z>~@+ItNkk=>g#Wtm?Xq3tKp8@QyoSp~xZ?xGb1ba}G%1ohXNWFji< zM8r_X^D-FVzagLXu^7Q(>_PeT%^AY1tA2w=o`VUyL6(F5MWOV#o@HK|h=&iF`LC*I8kmjY|aTOy_>U2s1= z2Yuz>qCEY|D3vG8evyMOYZ&N$Ql=}U61B`y|5f1Qf5uIV+(Bp1byFneZV5}x!d#xG z6Rg_4QA&SWrqB*n15Zo`Kt@4ZIc77<*0$EEdbjpccZr}VyRBsgY&I;2Uz6i)txHPZ z{ZI71B3<`Y3JF$T8w6wvh)!yq`HIFd_Fpcr^3Ln2w6V)uZ)>$Wdn|2m8}QyTikzR| z|5Rk(74@3Sa_2?;@@ovxH9lPejlhS$ZPf8p>Iw1t`BWH;Zyof1C#+hU#(5!=$q=I+ zZLrZUelJtv-|+Iy_H-+Wk;YUCE)JLw5_T5sj%t_qGNs}{Q#ZBxKyI_NbX;3Y#k35N zVtwy&i+D4wGIB;cbdB6Cd{o|Sx|LyQhuca^(VTlC)>AH?Bc1U%bEE>dRhq)q55f3r z9R#vFismx<{Gjzs@>D8Gn4;vp65!Y#l&_47)i_%AbNul3a>5o~n~O5TtkB1D(JKVj6sxDd;hFZ`10iAvCa02&=^aJR{jJL+3* z%y@1f`!jvSr6Ly1SBnH=Yi#nJjP;RhMi-gq%c!YAwaLv#t7J|KK!*gkOKj~MBUsC? zLHC>Ef(b^V>1+w!EMzMvf$CU4(H9YZdj6wP8du-ilMJ2}*(@+5mwX?AT9QV;^y%Kt oCuNK}+uQP=Dfs)r1Y-v*$QL~Uk1Kkr_|}uq{YOGL`;MRg3sjKxR{#J2 literal 0 HcmV?d00001 diff --git a/applications/plugins/metronome/img/wave_left_4x14.png b/applications/plugins/metronome/img/wave_left_4x14.png new file mode 100644 index 0000000000000000000000000000000000000000..beb2a611d240fb68c960f0453b11d3dec3a50794 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^EI`c1!2~4RIkfzN6k~CayA#8@b22YMvgwY#jtmSN z`?>!lvI6;>1s;*bKn;gMnDKc2iWH!rs;7%%h(vhu4}NAIhlT`43l26e_H&E4*!3EE jnV&mbIvhA~fPrE2w#elHp1mi48W=oX{an^LB{Ts5YlI{H literal 0 HcmV?d00001 diff --git a/applications/plugins/metronome/img/wave_right_4x14.png b/applications/plugins/metronome/img/wave_right_4x14.png new file mode 100644 index 0000000000000000000000000000000000000000..af249ee5b94055ba631122c6cd4588ca69e00b62 GIT binary patch literal 132 zcmeAS@N?(olHy`uVBq!ia0vp^EI`c1!2~4RIkfyi0_l#vjtmSN`?>!lvI6;>1s;*b zKsAR!nDKc2iWH!rnx~6nh(vfYL(_i)hDQf}DsY`U{@}pRkcSEq8Zs8#ry4i>`~RPx Yfj2vHxnZ!40#FBor>mdKI;Vst0FNsqT>t<8 literal 0 HcmV?d00001 diff --git a/applications/plugins/metronome/metronome.c b/applications/plugins/metronome/metronome.c new file mode 100644 index 000000000..e0770f2f8 --- /dev/null +++ b/applications/plugins/metronome/metronome.c @@ -0,0 +1,383 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "gui_extensions.h" + +#define BPM_STEP_SIZE_FINE 0.5d +#define BPM_STEP_SIZE_COARSE 10.0d +#define BPM_BOUNDARY_LOW 10.0d +#define BPM_BOUNDARY_HIGH 300.0d +#define BEEP_DELAY_MS 50 + +#define wave_bitmap_left_width 4 +#define wave_bitmap_left_height 14 +static uint8_t wave_bitmap_left_bits[] = + {0x08, 0x0C, 0x06, 0x06, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x06, 0x06, 0x0C, 0x08}; + +#define wave_bitmap_right_width 4 +#define wave_bitmap_right_height 14 +static uint8_t wave_bitmap_right_bits[] = + {0x01, 0x03, 0x06, 0x06, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x06, 0x06, 0x03, 0x01}; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +enum OutputMode { Loud, Vibro, Silent }; + +typedef struct { + double bpm; + bool playing; + int beats_per_bar; + int note_length; + int current_beat; + enum OutputMode output_mode; + FuriTimer* timer; + NotificationApp* notifications; +} MetronomeState; + +static void render_callback(Canvas* const canvas, void* ctx) { + const MetronomeState* metronome_state = acquire_mutex((ValueMutex*)ctx, 25); + if(metronome_state == NULL) { + return; + } + + string_t tempStr; + string_init(tempStr); + + canvas_draw_frame(canvas, 0, 0, 128, 64); + + canvas_set_font(canvas, FontPrimary); + + // draw bars/beat + string_printf(tempStr, "%d/%d", metronome_state->beats_per_bar, metronome_state->note_length); + canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignCenter, string_get_cstr(tempStr)); + string_reset(tempStr); + + // draw BPM value + string_printf(tempStr, "%.2f", metronome_state->bpm); + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned(canvas, 64, 24, AlignCenter, AlignCenter, string_get_cstr(tempStr)); + string_reset(tempStr); + + // draw volume indicator + // always draw first waves + canvas_draw_xbm( + canvas, 20, 17, wave_bitmap_left_width, wave_bitmap_left_height, wave_bitmap_left_bits); + canvas_draw_xbm( + canvas, + canvas_width(canvas) - 20 - wave_bitmap_right_width, + 17, + wave_bitmap_right_width, + wave_bitmap_right_height, + wave_bitmap_right_bits); + if(metronome_state->output_mode < Silent) { + canvas_draw_xbm( + canvas, 16, 17, wave_bitmap_left_width, wave_bitmap_left_height, wave_bitmap_left_bits); + canvas_draw_xbm( + canvas, + canvas_width(canvas) - 16 - wave_bitmap_right_width, + 17, + wave_bitmap_right_width, + wave_bitmap_right_height, + wave_bitmap_right_bits); + } + if(metronome_state->output_mode < Vibro) { + canvas_draw_xbm( + canvas, 12, 17, wave_bitmap_left_width, wave_bitmap_left_height, wave_bitmap_left_bits); + canvas_draw_xbm( + canvas, + canvas_width(canvas) - 12 - wave_bitmap_right_width, + 17, + wave_bitmap_right_width, + wave_bitmap_right_height, + wave_bitmap_right_bits); + } + // draw button prompts + canvas_set_font(canvas, FontSecondary); + elements_button_left(canvas, "Slow"); + elements_button_right(canvas, "Fast"); + if(metronome_state->playing) { + elements_button_center(canvas, "Stop "); + } else { + elements_button_center(canvas, "Start"); + } + elements_button_top_left(canvas, "Push"); + elements_button_top_right(canvas, "Hold"); + + // draw progress bar + elements_progress_bar( + canvas, 8, 36, 112, (float)metronome_state->current_beat / metronome_state->beats_per_bar); + + // cleanup + string_clear(tempStr); + release_mutex((ValueMutex*)ctx, metronome_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 timer_callback(void* ctx) { + // this is where we go BEEP! + MetronomeState* metronome_state = acquire_mutex((ValueMutex*)ctx, 25); + metronome_state->current_beat++; + if(metronome_state->current_beat > metronome_state->beats_per_bar) { + metronome_state->current_beat = 1; + } + if(metronome_state->current_beat == 1) { + // pronounced beat + notification_message(metronome_state->notifications, &sequence_set_only_red_255); + switch(metronome_state->output_mode) { + case Loud: + furi_hal_speaker_start(440.0f, 1.0f); + break; + case Vibro: + notification_message(metronome_state->notifications, &sequence_set_vibro_on); + break; + case Silent: + break; + } + } else { + // unpronounced beat + notification_message(metronome_state->notifications, &sequence_set_only_green_255); + switch(metronome_state->output_mode) { + case Loud: + furi_hal_speaker_start(220.0f, 1.0f); + break; + case Vibro: + notification_message(metronome_state->notifications, &sequence_set_vibro_on); + break; + case Silent: + break; + } + }; + + // this is a bit of a kludge... if we are on vibro and unpronounced, stop vibro after half the usual duration + switch(metronome_state->output_mode) { + case Loud: + furi_delay_ms(BEEP_DELAY_MS); + furi_hal_speaker_stop(); + break; + case Vibro: + if(metronome_state->current_beat == 1) { + furi_delay_ms(BEEP_DELAY_MS); + notification_message(metronome_state->notifications, &sequence_reset_vibro); + } else { + furi_delay_ms((int)BEEP_DELAY_MS / 2); + notification_message(metronome_state->notifications, &sequence_reset_vibro); + furi_delay_ms((int)BEEP_DELAY_MS / 2); + } + break; + case Silent: + break; + } + notification_message(metronome_state->notifications, &sequence_reset_rgb); + + release_mutex((ValueMutex*)ctx, metronome_state); +} + +static uint32_t state_to_sleep_ticks(MetronomeState* metronome_state) { + // calculate time between beeps + uint32_t tps = furi_kernel_get_tick_frequency(); + double multiplier = 4.0d / metronome_state->note_length; + double bps = (double)metronome_state->bpm / 60; + return (uint32_t)(round(tps / bps) - ((BEEP_DELAY_MS / 1000) * tps)) * multiplier; +} + +static void update_timer(MetronomeState* metronome_state) { + if(furi_timer_is_running(metronome_state->timer)) { + furi_timer_stop(metronome_state->timer); + furi_timer_start(metronome_state->timer, state_to_sleep_ticks(metronome_state)); + } +} + +static void increase_bpm(MetronomeState* metronome_state, double amount) { + metronome_state->bpm += amount; + if(metronome_state->bpm > (double)BPM_BOUNDARY_HIGH) { + metronome_state->bpm = BPM_BOUNDARY_HIGH; + } + update_timer(metronome_state); +} + +static void decrease_bpm(MetronomeState* metronome_state, double amount) { + metronome_state->bpm -= amount; + if(metronome_state->bpm < (double)BPM_BOUNDARY_LOW) { + metronome_state->bpm = BPM_BOUNDARY_LOW; + } + update_timer(metronome_state); +} + +static void cycle_beats_per_bar(MetronomeState* metronome_state) { + metronome_state->beats_per_bar++; + if(metronome_state->beats_per_bar > metronome_state->note_length) { + metronome_state->beats_per_bar = 1; + } +} + +static void cycle_note_length(MetronomeState* metronome_state) { + metronome_state->note_length *= 2; + if(metronome_state->note_length > 16) { + metronome_state->note_length = 2; + metronome_state->beats_per_bar = 1; + } + update_timer(metronome_state); +} + +static void cycle_output_mode(MetronomeState* metronome_state) { + metronome_state->output_mode++; + if(metronome_state->output_mode > Silent) { + metronome_state->output_mode = Loud; + } +} + +static void metronome_state_init(MetronomeState* const metronome_state) { + metronome_state->bpm = 120.0; + metronome_state->playing = false; + metronome_state->beats_per_bar = 4; + metronome_state->note_length = 4; + metronome_state->current_beat = 0; + metronome_state->output_mode = Loud; + metronome_state->notifications = furi_record_open(RECORD_NOTIFICATION); +} + +int32_t metronome_app() { + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + + MetronomeState* metronome_state = malloc(sizeof(MetronomeState)); + metronome_state_init(metronome_state); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, metronome_state, sizeof(MetronomeState))) { + FURI_LOG_E("Metronome", "cannot create mutex\r\n"); + free(metronome_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); + metronome_state->timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, &state_mutex); + + // Open GUI and register view_port + Gui* gui = furi_record_open("gui"); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + PluginEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + + MetronomeState* metronome_state = (MetronomeState*)acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + if(event.type == EventTypeKey) { + if(event.input.type == InputTypeShort) { + // push events + switch(event.input.key) { + case InputKeyUp: + cycle_beats_per_bar(metronome_state); + break; + case InputKeyDown: + cycle_output_mode(metronome_state); + break; + case InputKeyRight: + increase_bpm(metronome_state, BPM_STEP_SIZE_FINE); + break; + case InputKeyLeft: + decrease_bpm(metronome_state, BPM_STEP_SIZE_FINE); + break; + case InputKeyOk: + metronome_state->playing = !metronome_state->playing; + if(metronome_state->playing) { + furi_timer_start( + metronome_state->timer, state_to_sleep_ticks(metronome_state)); + } else { + furi_timer_stop(metronome_state->timer); + } + break; + case InputKeyBack: + processing = false; + break; + } + } else if(event.input.type == InputTypeLong) { + // hold events + switch(event.input.key) { + case InputKeyUp: + cycle_note_length(metronome_state); + break; + case InputKeyDown: + break; + case InputKeyRight: + increase_bpm(metronome_state, BPM_STEP_SIZE_COARSE); + break; + case InputKeyLeft: + decrease_bpm(metronome_state, BPM_STEP_SIZE_COARSE); + break; + case InputKeyOk: + break; + case InputKeyBack: + processing = false; + break; + } + } else if(event.input.type == InputTypeRepeat) { + // repeat events + switch(event.input.key) { + case InputKeyUp: + break; + case InputKeyDown: + break; + case InputKeyRight: + increase_bpm(metronome_state, BPM_STEP_SIZE_COARSE); + break; + case InputKeyLeft: + decrease_bpm(metronome_state, BPM_STEP_SIZE_COARSE); + break; + case InputKeyOk: + break; + case InputKeyBack: + processing = false; + break; + } + } + } + } else { + FURI_LOG_D("Metronome", "FuriMessageQueue: event timeout"); + // event timeout + } + + view_port_update(view_port); + release_mutex(&state_mutex, metronome_state); + } + + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close("gui"); + view_port_free(view_port); + furi_message_queue_free(event_queue); + delete_mutex(&state_mutex); + furi_timer_free(metronome_state->timer); + furi_record_close(RECORD_NOTIFICATION); + free(metronome_state); + + return 0; +} diff --git a/applications/plugins/metronome/metronome_icon.png b/applications/plugins/metronome/metronome_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..64d0ddbe9466604facc5a3642c7be6d0dc172fac GIT binary patch literal 170 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ihk44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`0h7I;J!GcfQS24TkI`72U@g7%&+jv*SswcU$y;(nnzwBQ1s_>nGsd&Au*G7W~FZa)`x2%NTv2S41RNmv#pRAz{G>XB~ L)z4*}Q$iB} + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/applications/plugins/minesweeper/README.md b/applications/plugins/minesweeper/README.md new file mode 100644 index 000000000..ab253a4a6 --- /dev/null +++ b/applications/plugins/minesweeper/README.md @@ -0,0 +1,19 @@ +# Minesweeper + +[Original Link](https://github.com/panki27/minesweeper) + +This is a Minesweeper implementation for the Flipper Zero device. + +![screenshot](img/screenshot.png) + +## Controls + +- Arrow buttons to move +- Push center button to open field +- Hold center button to toggle flag + +## Compiling + +``` +./fbt firmware_minesweeper +``` diff --git a/applications/plugins/minesweeper/application.fam b/applications/plugins/minesweeper/application.fam new file mode 100644 index 000000000..405cb0d1c --- /dev/null +++ b/applications/plugins/minesweeper/application.fam @@ -0,0 +1,12 @@ +App( + appid="Minesweeper", + name="Minesweeper", + apptype=FlipperAppType.EXTERNAL, + entry_point="minesweeper_app", + cdefines=["APP_MINESWEEPER"], + requires=["gui"], + stack_size=8 * 1024, + fap_category="Games", + fap_icon="minesweeper_icon.png", + order=35, +) diff --git a/applications/plugins/minesweeper/assets.h b/applications/plugins/minesweeper/assets.h new file mode 100644 index 000000000..b734f240f --- /dev/null +++ b/applications/plugins/minesweeper/assets.h @@ -0,0 +1,144 @@ +#define tile_0_width 8 +#define tile_0_height 8 +static uint8_t tile_0_bits[] = { + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; +#define tile_1_width 8 +#define tile_1_height 8 +static uint8_t tile_1_bits[] = { + 0x00, + 0x10, + 0x18, + 0x10, + 0x10, + 0x10, + 0x10, + 0x00, +}; +#define tile_2_width 8 +#define tile_2_height 8 +static uint8_t tile_2_bits[] = { + 0x00, + 0x1C, + 0x20, + 0x20, + 0x18, + 0x04, + 0x3C, + 0x00, +}; +#define tile_3_width 8 +#define tile_3_height 8 +static uint8_t tile_3_bits[] = { + 0x00, + 0x1C, + 0x20, + 0x20, + 0x18, + 0x20, + 0x1C, + 0x00, +}; +#define tile_4_width 8 +#define tile_4_height 8 +static uint8_t tile_4_bits[] = { + 0x00, + 0x04, + 0x14, + 0x14, + 0x3C, + 0x10, + 0x10, + 0x00, +}; +#define tile_5_width 8 +#define tile_5_height 8 +static uint8_t tile_5_bits[] = { + 0x00, + 0x3C, + 0x04, + 0x1C, + 0x20, + 0x20, + 0x1C, + 0x00, +}; +#define tile_6_width 8 +#define tile_6_height 8 +static uint8_t tile_6_bits[] = { + 0x00, + 0x18, + 0x24, + 0x04, + 0x1C, + 0x24, + 0x18, + 0x00, +}; +#define tile_7_width 8 +#define tile_7_height 8 +static uint8_t tile_7_bits[] = { + 0x00, + 0x3C, + 0x20, + 0x20, + 0x10, + 0x08, + 0x08, + 0x00, +}; +#define tile_8_width 8 +#define tile_8_height 8 +static uint8_t tile_8_bits[] = { + 0x00, + 0x18, + 0x24, + 0x18, + 0x24, + 0x24, + 0x18, + 0x00, +}; +#define tile_flag_width 8 +#define tile_flag_height 8 +static uint8_t tile_flag_bits[] = { + 0xFF, + 0x81, + 0xB9, + 0x89, + 0x89, + 0x9D, + 0x81, + 0xFF, +}; +#define tile_mine_width 8 +#define tile_mine_height 8 +static uint8_t tile_mine_bits[] = { + 0x55, + 0xAA, + 0x55, + 0xAA, + 0x55, + 0xAA, + 0x55, + 0xAA, +}; +#define tile_uncleared_width 8 +#define tile_uncleared_height 8 +static uint8_t tile_uncleared_bits[] = { + 0xFF, + 0x81, + 0x81, + 0x81, + 0x81, + 0x81, + 0x81, + 0xFF, +}; diff --git a/applications/plugins/minesweeper/assets/asset b/applications/plugins/minesweeper/assets/asset new file mode 100644 index 000000000..80a6c89f5 --- /dev/null +++ b/applications/plugins/minesweeper/assets/asset @@ -0,0 +1,48 @@ +#define tile_0_width 8 +#define tile_0_height 8 +static uint8_t tile_0_bits[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }; +#define tile_1_width 8 +#define tile_1_height 8 +static uint8_t tile_1_bits[] = { + 0x00, 0x10, 0x18, 0x10, 0x10, 0x10, 0x10, 0x00, }; +#define tile_2_width 8 +#define tile_2_height 8 +static uint8_t tile_2_bits[] = { + 0x00, 0x1C, 0x20, 0x20, 0x18, 0x04, 0x3C, 0x00, }; +#define tile_3_width 8 +#define tile_3_height 8 +static uint8_t tile_3_bits[] = { + 0x00, 0x1C, 0x20, 0x20, 0x18, 0x20, 0x1C, 0x00, }; +#define tile_4_width 8 +#define tile_4_height 8 +static uint8_t tile_4_bits[] = { + 0x00, 0x04, 0x14, 0x14, 0x3C, 0x10, 0x10, 0x00, }; +#define tile_5_width 8 +#define tile_5_height 8 +static uint8_t tile_5_bits[] = { + 0x00, 0x3C, 0x04, 0x1C, 0x20, 0x20, 0x1C, 0x00, }; +#define tile_6_width 8 +#define tile_6_height 8 +static uint8_t tile_6_bits[] = { + 0x00, 0x18, 0x24, 0x04, 0x1C, 0x24, 0x18, 0x00, }; +#define tile_7_width 8 +#define tile_7_height 8 +static uint8_t tile_7_bits[] = { + 0x00, 0x3C, 0x20, 0x20, 0x10, 0x08, 0x08, 0x00, }; +#define tile_8_width 8 +#define tile_8_height 8 +static uint8_t tile_8_bits[] = { + 0x00, 0x18, 0x24, 0x18, 0x24, 0x24, 0x18, 0x00, }; +#define tile_flag_width 8 +#define tile_flag_height 8 +static uint8_t tile_flag_bits[] = { + 0xFF, 0x81, 0xB9, 0x89, 0x89, 0x9D, 0x81, 0xFF, }; +#define tile_mine_width 8 +#define tile_mine_height 8 +static uint8_t tile_mine_bits[] = { + 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, }; +#define tile_uncleared_width 8 +#define tile_uncleared_height 8 +static uint8_t tile_uncleared_bits[] = { + 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF, }; diff --git a/applications/plugins/minesweeper/assets/mockup.png b/applications/plugins/minesweeper/assets/mockup.png new file mode 100644 index 0000000000000000000000000000000000000000..00d99b1f2d6228001a7abf4415cb3982dec58b6d GIT binary patch literal 400 zcmeAS@N?(olHy`uVBq!ia0vp^4M6O`!2~2@x4h5*QjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`ISV`@iy0XB4ude`@%$AjKn<#%E{-7;ac?hg|U>wZs4b literal 0 HcmV?d00001 diff --git a/applications/plugins/minesweeper/assets/tile_0.png b/applications/plugins/minesweeper/assets/tile_0.png new file mode 100644 index 0000000000000000000000000000000000000000..c7fec6cca0da73d9386a91023681960d62d9371d GIT binary patch literal 130 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sDEfH31!Z9ZwBptz@tV~9p@@*jQ{o(cxxDaSb5nwi+x7^L%9 Vc#3`amjRV9c)I$ztaD0e0sxn193TJy literal 0 HcmV?d00001 diff --git a/applications/plugins/minesweeper/assets/tile_0.xbm b/applications/plugins/minesweeper/assets/tile_0.xbm new file mode 100644 index 000000000..3da1fa7d7 --- /dev/null +++ b/applications/plugins/minesweeper/assets/tile_0.xbm @@ -0,0 +1,4 @@ +#define tile_0_width 8 +#define tile_0_height 8 +static char tile_0_bits[] = { + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, }; diff --git a/applications/plugins/minesweeper/assets/tile_1.png b/applications/plugins/minesweeper/assets/tile_1.png new file mode 100644 index 0000000000000000000000000000000000000000..588d77c2ad3b7d00413646b5316eca6e3f7b9f33 GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBufiR<}hF1enP}kGNF+?Lc`42w}PX&YUls0BJH#ZG#rT~!z r-c1Z$OgY;UBO3WK7BF0u<>6rvaOC1CY7TVbP0l+XkKYiuIA literal 0 HcmV?d00001 diff --git a/applications/plugins/minesweeper/assets/tile_1.xbm b/applications/plugins/minesweeper/assets/tile_1.xbm new file mode 100644 index 000000000..568c72155 --- /dev/null +++ b/applications/plugins/minesweeper/assets/tile_1.xbm @@ -0,0 +1,4 @@ +#define tile_1_width 8 +#define tile_1_height 8 +static uint8_t tile_1_bits[] = { + 0x00, 0x10, 0x18, 0x10, 0x10, 0x10, 0x10, 0x00, }; diff --git a/applications/plugins/minesweeper/assets/tile_2.png b/applications/plugins/minesweeper/assets/tile_2.png new file mode 100644 index 0000000000000000000000000000000000000000..9c1c590131adc294d658bcb17ad7ca13ea27538e GIT binary patch literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBufiR<}hF1en(9+YzF+?Lc`42w}PX&YUl(dADl$PX_hN~;|!-3g6JPa9~@@(wq-;@K5VeoYIb6Mw< G&;$SlgDL(1 literal 0 HcmV?d00001 diff --git a/applications/plugins/minesweeper/assets/tile_3.xbm b/applications/plugins/minesweeper/assets/tile_3.xbm new file mode 100644 index 000000000..022bd3c6b --- /dev/null +++ b/applications/plugins/minesweeper/assets/tile_3.xbm @@ -0,0 +1,4 @@ +#define tile_3_width 8 +#define tile_3_height 8 +static uint8_t tile_3_bits[] = { + 0x00, 0x1C, 0x20, 0x20, 0x18, 0x20, 0x1C, 0x00, }; diff --git a/applications/plugins/minesweeper/assets/tile_4.png b/applications/plugins/minesweeper/assets/tile_4.png new file mode 100644 index 0000000000000000000000000000000000000000..dfaab7b272c4afc65b30f4d6e8e3d2e511dfd900 GIT binary patch literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBufiR<}hF1en(9+YzF+?Lc`42w}PX&YUl(dADl$K2aj4_Nn z3$%{}H1Gw894Jwq%@od1qbuNena zL>gEUCD;N)8hjkhIaf!lvI6;>1s;*b3=DjSL74G){)!Z!poFK3V~9j}GDFjU35MMoEIeyi%AOk;0F_MI V?3?*H{vc2dgQu&X%Q~loCIJ57AM*eJ literal 0 HcmV?d00001 diff --git a/applications/plugins/minesweeper/assets/tile_flag.png b/applications/plugins/minesweeper/assets/tile_flag.png new file mode 100644 index 0000000000000000000000000000000000000000..6ec2a71f82b3a8d3f0b4923b1693241933be52b9 GIT binary patch literal 170 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBufiR<}hF1en(B9L?Q`cmH2X`)4vG&WJ}Z rRN8Ud{7Y^V-Y(xFnDSdQ*M^;&xov*ptM1)7K#dHZu6{1-oD!M!lvI6;>1s;*b3=DjSL74G){)!Z!popi7V~9j}@&WdL$qfe;8d(htfGYX2r&gWJ R{{xg~@O1TaS?83{1OSrF9}EBh literal 0 HcmV?d00001 diff --git a/applications/plugins/minesweeper/assets/tile_uncleared.xbm b/applications/plugins/minesweeper/assets/tile_uncleared.xbm new file mode 100644 index 000000000..e8a8ef610 --- /dev/null +++ b/applications/plugins/minesweeper/assets/tile_uncleared.xbm @@ -0,0 +1,4 @@ +#define tile_uncleared_width 8 +#define tile_uncleared_height 8 +static uint8_t tile_uncleared_bits[] = { + 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF, }; diff --git a/applications/plugins/minesweeper/img/screenshot.png b/applications/plugins/minesweeper/img/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..65b307c550cdfc13c388135b4e286d1be56957d3 GIT binary patch literal 1842 zcmbtVc~BEq7=IyRgcgu0lNROZV5T#7|k6~v9}^R?EPJl zBCckac0tDKDHDB)&E87M8+q{t=h*b7A$KRhV%w@b}PgcgWS4FPo7UFZGEXhdN81u1de_LK+PP$ zsQ-lrLXhnzD}BG1!on)ucSr&5&)qBH2AsQ8O9t~kdlSfcO9bQd0KiyA0`oTkaCD$* zYv;&Vc~b;u-O9C;8JSC^LE_FVG}yv)al|S=f}-Piv@|@)jMR1z+6vs<-w#_hljONO z0y(-b+wpdTA<@2EE3JbPcZMdMt1(zwv-tGq7Slq=_!UaQ;gPGcAapi5eE&Xruf!V! zpK)f><@h6VOJ|yyOzwdL9)IaB*Nj!2ZH*It5;K!Y7hY#C0)y;nUF72lEu|g~9eoWH zVk*m6&SN@cT)ZnLOyjszN@qLv+_4|VOE8tgDia9!kbn7(XrV9b-ZVR3(}T*;MfLXO zM{uqFcnD-V^x8 z-7(UvLMBPwi*?*-KLlypY%534XE{Fi*P(OwbB?`QAzkNpVq&I@1M2Lr}c&4dcO`0E>xN~O!>eF(XAfJP?lz7M+ z{8TLMyVVV$L-+Yq|4AbyOTu^99Qx=h1GwsOVWT|;D|x3ebem5#-gc%84>74@kwJE* zBMPy+g8Q=UA^D+}-GJN)Kw9+<`NU{lIMfx+uP+q%o}cseKD+O~n}cOMa6+bTt`=jr zaLA+XKn6K^Fq!8!dVJMOh)wbic<}$%y3dM$w7%+c270qNN5ILOmVXTuKM5rV zZ4LU!2&y7VaFM__WMhuxyNFVavqwiE-MxVfj3-(!!srt~tfN5MLIkp5Tb;A^0M@y&|tLV8cCM1-cd+b59W z;+(jX&}2Fjk;9}>73wszruDURZgYMk@L4mlH?JaSv{b+_V;rQ^E#u;U`kns)=Ov_G z85b1lcsCl*l=K`vreg`zY7U9?mh0Yf*Bk14kL`@TnP=zI>$Y&a6)#NaYnFC62x6qc znb;9!nsMo2(!ak7Cs(@|<}H=J-*(36lkctWE&l1cC7M!Smn30bQoLeK?vZ~0WMt2h literal 0 HcmV?d00001 diff --git a/applications/plugins/minesweeper/minesweeper.c b/applications/plugins/minesweeper/minesweeper.c new file mode 100644 index 000000000..e825dfcdc --- /dev/null +++ b/applications/plugins/minesweeper/minesweeper.c @@ -0,0 +1,527 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "assets.h" + +#define PLAYFIELD_WIDTH 16 +#define PLAYFIELD_HEIGHT 7 +#define TILE_WIDTH 8 +#define TILE_HEIGHT 8 + +#define MINECOUNT 20 + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +typedef enum { + TileType0, // this HAS to be in order, for hint assignment to be ez pz + TileType1, + TileType2, + TileType3, + TileType4, + TileType5, + TileType6, + TileType7, + TileType8, + TileTypeUncleared, + TileTypeFlag, + TileTypeMine +} TileType; + +typedef enum { + FieldEmpty, // <-- same goes for this + FieldMine +} Field; + +typedef struct { + Field minefield[PLAYFIELD_WIDTH][PLAYFIELD_HEIGHT]; + TileType playfield[PLAYFIELD_WIDTH][PLAYFIELD_HEIGHT]; + FuriTimer* timer; + int cursor_x; + int cursor_y; + int mines_left; + int fields_cleared; + int flags_set; + bool game_started; + uint32_t game_started_tick; +} Minesweeper; + +static void timer_callback(void* ctx) { + UNUSED(ctx); + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + notification_message(notification, &sequence_reset_vibro); + furi_record_close(RECORD_NOTIFICATION); +} + +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 render_callback(Canvas* const canvas, void* ctx) { + const Minesweeper* minesweeper_state = acquire_mutex((ValueMutex*)ctx, 25); + if(minesweeper_state == NULL) { + return; + } + FuriString* mineStr; + FuriString* timeStr; + mineStr = furi_string_alloc(); + timeStr = furi_string_alloc(); + + furi_string_printf(mineStr, "Mines: %d", MINECOUNT - minesweeper_state->flags_set); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(mineStr)); + + int seconds = 0; + int minutes = 0; + if(minesweeper_state->game_started) { + uint32_t ticks_elapsed = furi_get_tick() - minesweeper_state->game_started_tick; + seconds = (int)ticks_elapsed / furi_kernel_get_tick_frequency(); + minutes = (int)seconds / 60; + seconds = seconds % 60; + } + furi_string_printf(timeStr, "%01d:%02d", minutes, seconds); + canvas_draw_str_aligned(canvas, 128, 0, AlignRight, AlignTop, furi_string_get_cstr(timeStr)); + + for(int y = 0; y < PLAYFIELD_HEIGHT; y++) { + for(int x = 0; x < PLAYFIELD_WIDTH; x++) { + if(x == minesweeper_state->cursor_x && y == minesweeper_state->cursor_y) { + canvas_invert_color(canvas); + } + switch(minesweeper_state->playfield[x][y]) { + case TileType0: + canvas_draw_xbm( + canvas, + x * TILE_HEIGHT, // x + 8 + (y * TILE_WIDTH), // y + TILE_WIDTH, + TILE_HEIGHT, + tile_0_bits); + break; + case TileType1: + canvas_draw_xbm( + canvas, + x * TILE_HEIGHT, // x + 8 + (y * TILE_WIDTH), // y + TILE_WIDTH, + TILE_HEIGHT, + tile_1_bits); + break; + case TileType2: + canvas_draw_xbm( + canvas, + x * TILE_HEIGHT, // x + 8 + (y * TILE_WIDTH), // y + TILE_WIDTH, + TILE_HEIGHT, + tile_2_bits); + break; + case TileType3: + canvas_draw_xbm( + canvas, + x * TILE_HEIGHT, // x + 8 + (y * TILE_WIDTH), // y + TILE_WIDTH, + TILE_HEIGHT, + tile_3_bits); + break; + case TileType4: + canvas_draw_xbm( + canvas, + x * TILE_HEIGHT, // x + 8 + (y * TILE_WIDTH), // y + TILE_WIDTH, + TILE_HEIGHT, + tile_4_bits); + break; + case TileType5: + canvas_draw_xbm( + canvas, + x * TILE_HEIGHT, // x + 8 + (y * TILE_WIDTH), // y + TILE_WIDTH, + TILE_HEIGHT, + tile_5_bits); + break; + case TileType6: + canvas_draw_xbm( + canvas, + x * TILE_HEIGHT, // x + 8 + (y * TILE_WIDTH), // y + TILE_WIDTH, + TILE_HEIGHT, + tile_6_bits); + break; + case TileType7: + canvas_draw_xbm( + canvas, + x * TILE_HEIGHT, // x + 8 + (y * TILE_WIDTH), // y + TILE_WIDTH, + TILE_HEIGHT, + tile_7_bits); + break; + case TileType8: + canvas_draw_xbm( + canvas, + x * TILE_HEIGHT, // x + 8 + (y * TILE_WIDTH), // y + TILE_WIDTH, + TILE_HEIGHT, + tile_8_bits); + break; + case TileTypeFlag: + canvas_draw_xbm( + canvas, + x * TILE_HEIGHT, // x + 8 + (y * TILE_WIDTH), // y + TILE_WIDTH, + TILE_HEIGHT, + tile_flag_bits); + break; + case TileTypeUncleared: + canvas_draw_xbm( + canvas, + x * TILE_HEIGHT, // x + 8 + (y * TILE_WIDTH), // y + TILE_WIDTH, + TILE_HEIGHT, + tile_uncleared_bits); + break; + case TileTypeMine: + canvas_draw_xbm( + canvas, + x * TILE_HEIGHT, // x + 8 + (y * TILE_WIDTH), // y + TILE_WIDTH, + TILE_HEIGHT, + tile_mine_bits); + break; + } + if(x == minesweeper_state->cursor_x && y == minesweeper_state->cursor_y) { + canvas_invert_color(canvas); + } + } + } + + furi_string_free(mineStr); + furi_string_free(timeStr); + release_mutex((ValueMutex*)ctx, minesweeper_state); +} + +static void setup_playfield(Minesweeper* minesweeper_state) { + int mines_left = MINECOUNT; + for(int y = 0; y < PLAYFIELD_HEIGHT; y++) { + for(int x = 0; x < PLAYFIELD_WIDTH; x++) { + minesweeper_state->minefield[x][y] = FieldEmpty; + minesweeper_state->playfield[x][y] = TileTypeUncleared; + } + } + while(mines_left > 0) { + int rand_x = rand() % PLAYFIELD_WIDTH; + int rand_y = rand() % PLAYFIELD_HEIGHT; + // make sure first guess isn't a mine + if(minesweeper_state->minefield[rand_x][rand_y] == FieldEmpty && + (minesweeper_state->cursor_x != rand_x && minesweeper_state->cursor_y != rand_y)) { + minesweeper_state->minefield[rand_x][rand_y] = FieldMine; + mines_left--; + } + } + minesweeper_state->mines_left = MINECOUNT; + minesweeper_state->fields_cleared = 0; + minesweeper_state->flags_set = 0; + minesweeper_state->game_started_tick = furi_get_tick(); + minesweeper_state->game_started = false; +} + +static void place_flag(Minesweeper* minesweeper_state) { + if(minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] == + TileTypeUncleared) { + minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] = + TileTypeFlag; + minesweeper_state->flags_set++; + } else if( + minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] == + TileTypeFlag) { + minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] = + TileTypeUncleared; + minesweeper_state->flags_set--; + } +} + +static bool game_lost(Minesweeper* minesweeper_state) { + // returns true if the player wants to restart, otherwise false + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + + DialogMessage* message = dialog_message_alloc(); + const char* header_text = "Game Over"; + const char* message_text = "You hit a mine!"; + + dialog_message_set_header(message, header_text, 64, 3, AlignCenter, AlignTop); + dialog_message_set_text(message, message_text, 64, 32, AlignCenter, AlignCenter); + dialog_message_set_buttons(message, NULL, "Play again", NULL); + + dialog_message_set_icon(message, NULL, 0, 10); + + NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION); + notification_message(notifications, &sequence_set_vibro_on); + furi_record_close(RECORD_NOTIFICATION); + furi_timer_start(minesweeper_state->timer, (uint32_t)furi_kernel_get_tick_frequency() * 0.2); + + DialogMessageButton choice = dialog_message_show(dialogs, message); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + + return choice == DialogMessageButtonCenter; +} + +static bool game_won(Minesweeper* minesweeper_state) { + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + + FuriString* tempStr; + tempStr = furi_string_alloc(); + + int seconds = 0; + int minutes = 0; + uint32_t ticks_elapsed = furi_get_tick() - minesweeper_state->game_started_tick; + seconds = (int)ticks_elapsed / furi_kernel_get_tick_frequency(); + minutes = (int)seconds / 60; + seconds = seconds % 60; + + DialogMessage* message = dialog_message_alloc(); + const char* header_text = "Game won!"; + furi_string_cat_printf(tempStr, "Minefield cleared in %01d:%02d", minutes, seconds); + dialog_message_set_header(message, header_text, 64, 3, AlignCenter, AlignTop); + dialog_message_set_text( + message, furi_string_get_cstr(tempStr), 64, 32, AlignCenter, AlignCenter); + dialog_message_set_buttons(message, NULL, "Play again", NULL); + // TODO: create icon + dialog_message_set_icon(message, NULL, 72, 17); + + DialogMessageButton choice = dialog_message_show(dialogs, message); + dialog_message_free(message); + furi_string_free(tempStr); + furi_record_close(RECORD_DIALOGS); + return choice == DialogMessageButtonCenter; +} + +static bool play_move(Minesweeper* minesweeper_state, int cursor_x, int cursor_y) { + if(minesweeper_state->playfield[cursor_x][cursor_y] != TileTypeUncleared) { + // we're on an already uncovered field + return true; + } + if(minesweeper_state->minefield[cursor_x][cursor_y] == FieldMine) { + // TODO: player loses! + minesweeper_state->playfield[cursor_x][cursor_y] = TileTypeMine; + return false; + } else { + // get number of surrounding mines. + int hint = 0; + for(int y = cursor_y - 1; y <= cursor_y + 1; y++) { + for(int x = cursor_x - 1; x <= cursor_x + 1; x++) { + if(x == cursor_x && y == cursor_y) { + // we're on the cell the user selected, so ignore. + continue; + } + // make sure we don't go OOB + if(x >= 0 && x < PLAYFIELD_WIDTH && y >= 0 && y < PLAYFIELD_HEIGHT) { + if(minesweeper_state->minefield[x][y] == FieldMine) { + hint++; + } + } + } + } + // 〜( ̄▽ ̄〜) don't judge me (〜 ̄▽ ̄)〜 + minesweeper_state->playfield[cursor_x][cursor_y] = hint; + minesweeper_state->fields_cleared++; + FURI_LOG_D("Minesweeper", "Setting %d,%d to %d", cursor_x, cursor_y, hint); + if(hint == 0) { + // auto open surrounding fields. + for(int auto_y = cursor_y - 1; auto_y <= cursor_y + 1; auto_y++) { + for(int auto_x = cursor_x - 1; auto_x <= cursor_x + 1; auto_x++) { + if(auto_x == cursor_x && auto_y == cursor_y) { + continue; + } + if(auto_x >= 0 && auto_x < PLAYFIELD_WIDTH && auto_y >= 0 && + auto_y < PLAYFIELD_HEIGHT) { + if(minesweeper_state->playfield[auto_x][auto_y] == TileTypeUncleared) { + play_move(minesweeper_state, auto_x, auto_y); + } + } + } + } + } + return true; + } +} + +static void minesweeper_state_init(Minesweeper* const minesweeper_state) { + minesweeper_state->cursor_x = minesweeper_state->cursor_y = 0; + minesweeper_state->game_started = false; + for(int y = 0; y < PLAYFIELD_HEIGHT; y++) { + for(int x = 0; x < PLAYFIELD_WIDTH; x++) { + minesweeper_state->playfield[x][y] = TileTypeUncleared; + } + } +} + +int32_t minesweeper_app(void* p) { + UNUSED(p); + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + + DialogMessage* message = dialog_message_alloc(); + const char* header_text = "Minesweeper"; + const char* message_text = "Hold OK pressed to toggle flags.\ngithub.com/panki27"; + + dialog_message_set_header(message, header_text, 64, 3, AlignCenter, AlignTop); + dialog_message_set_text(message, message_text, 64, 32, AlignCenter, AlignCenter); + dialog_message_set_buttons(message, NULL, "Play", NULL); + + dialog_message_set_icon(message, NULL, 0, 10); + + dialog_message_show(dialogs, message); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + + Minesweeper* minesweeper_state = malloc(sizeof(Minesweeper)); + // setup + minesweeper_state_init(minesweeper_state); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, minesweeper_state, sizeof(minesweeper_state))) { + FURI_LOG_E("Minesweeper", "cannot create mutex\r\n"); + free(minesweeper_state); + return 255; + } + // BEGIN IMPLEMENTATION + + // 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); + minesweeper_state->timer = furi_timer_alloc(timer_callback, FuriTimerTypeOnce, &state_mutex); + + // Open GUI and register view_port + Gui* gui = furi_record_open("gui"); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + PluginEvent event; + for(bool processing = true; processing;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + Minesweeper* minesweeper_state = (Minesweeper*)acquire_mutex_block(&state_mutex); + if(event_status == FuriStatusOk) { + // press events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypeShort) { + switch(event.input.key) { + case InputKeyUp: + minesweeper_state->cursor_y--; + if(minesweeper_state->cursor_y < 0) { + minesweeper_state->cursor_y = 0; + } + break; + case InputKeyDown: + minesweeper_state->cursor_y++; + if(minesweeper_state->cursor_y >= PLAYFIELD_HEIGHT) { + minesweeper_state->cursor_y = PLAYFIELD_HEIGHT - 1; + } + break; + case InputKeyRight: + minesweeper_state->cursor_x++; + if(minesweeper_state->cursor_x >= PLAYFIELD_WIDTH) { + minesweeper_state->cursor_x = PLAYFIELD_WIDTH - 1; + } + break; + case InputKeyLeft: + minesweeper_state->cursor_x--; + if(minesweeper_state->cursor_x < 0) { + minesweeper_state->cursor_x = 0; + } + break; + case InputKeyOk: + if(!minesweeper_state->game_started) { + setup_playfield(minesweeper_state); + minesweeper_state->game_started = true; + } + if(!play_move( + minesweeper_state, + minesweeper_state->cursor_x, + minesweeper_state->cursor_y)) { + // ooops. looks like we hit a mine! + if(game_lost(minesweeper_state)) { + // player wants to restart. + setup_playfield(minesweeper_state); + } else { + // player wants to exit :( + processing = false; + } + } else { + // check win condition. + if(minesweeper_state->fields_cleared == + (PLAYFIELD_HEIGHT * PLAYFIELD_WIDTH) - MINECOUNT) { + if(game_won(minesweeper_state)) { + //player wants to restart + setup_playfield(minesweeper_state); + } else { + processing = false; + } + } + } + break; + case InputKeyBack: + // Exit the plugin + processing = false; + break; + } + } else if(event.input.type == InputTypeLong) { + // hold events + FURI_LOG_D("Minesweeper", "Got a long press!"); + switch(event.input.key) { + case InputKeyUp: + case InputKeyDown: + case InputKeyRight: + case InputKeyLeft: + break; + case InputKeyOk: + FURI_LOG_D("Minesweeper", "Toggling flag"); + place_flag(minesweeper_state); + break; + case InputKeyBack: + processing = false; + break; + } + } + } + } + view_port_update(view_port); + release_mutex(&state_mutex, minesweeper_state); + } + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close("gui"); + view_port_free(view_port); + furi_message_queue_free(event_queue); + delete_mutex(&state_mutex); + furi_timer_free(minesweeper_state->timer); + free(minesweeper_state); + + return 0; +} diff --git a/applications/plugins/minesweeper/minesweeper_icon.png b/applications/plugins/minesweeper/minesweeper_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8a7cd94681428ea135d091d7e8a8716d997e943b GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ihk44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`0h7I;J!GcfQS24TkI`72U@f@YpBjv*SsTTeQ2F(~jbAN=$G@~jzQOsx6m z3dL6^27FnZ<#@4w6|1ZLDlw72FP43&t@i0JWsvq4oSOaSnotifications, &sequence_display_backlight_on); notification_message(instance->notifications, &sequence_double_vibro); - scene_manager_next_scene(instance->scene_manager, SubBruteSceneSetupAttack); } else if( event.event == SubBruteCustomEventTypeTransmitNotStarted || event.event == SubBruteCustomEventTypeBackPressed) { - if (subbrute_worker_is_running(instance->worker)) { + if(subbrute_worker_is_running(instance->worker)) { // Notify notification_message(instance->notifications, &sequence_single_vibro); } diff --git a/applications/plugins/subbrute/scenes/subbrute_scene_save_name.c b/applications/plugins/subbrute/scenes/subbrute_scene_save_name.c index b5919ae5a..bb129e948 100644 --- a/applications/plugins/subbrute/scenes/subbrute_scene_save_name.c +++ b/applications/plugins/subbrute/scenes/subbrute_scene_save_name.c @@ -47,9 +47,14 @@ bool subbrute_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(strcmp(instance->text_store, "")) { furi_string_reset(instance->file_path); furi_string_cat_printf( - instance->file_path, "%s/%s%s", SUBBRUTE_PATH, instance->text_store, SUBBRUTE_FILE_EXT); + instance->file_path, + "%s/%s%s", + SUBBRUTE_PATH, + instance->text_store, + SUBBRUTE_FILE_EXT); - if(subbrute_device_save_file(instance->device, furi_string_get_cstr(instance->file_path))) { + if(subbrute_device_save_file( + instance->device, furi_string_get_cstr(instance->file_path))) { scene_manager_next_scene(instance->scene_manager, SubBruteSceneSaveSuccess); success = true; consumed = true; diff --git a/applications/plugins/totp/.clang-format b/applications/plugins/totp/.clang-format new file mode 100644 index 000000000..4b76f7fa4 --- /dev/null +++ b/applications/plugins/totp/.clang-format @@ -0,0 +1,191 @@ +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: AlwaysBreak +AlignArrayOfStructures: None +AlignConsecutiveMacros: None +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 99 +CommentPragmas: '^ IWYU pragma:' +QualifierAlignment: Leave +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +PackConstructorInitializers: BinPack +BasedOnStyle: '' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowAllConstructorInitializersOnNextLine: true +FixNamespaceComments: false +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 4 +IndentWrappedFunctionNames: true +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 10 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Left +PPIndentWidth: -1 +ReferenceAlignment: Pointer +ReflowComments: false +RemoveBracesLLVM: false +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: Never +SortJavaStaticImport: Before +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: Never +SpaceBeforeParensOptions: + AfterControlStatements: false + AfterForeachMacros: false + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: false + AfterOverloadedOperator: false + BeforeNonEmptyParentheses: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: c++03 +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/applications/plugins/totp/application.fam b/applications/plugins/totp/application.fam new file mode 100644 index 000000000..08f54619c --- /dev/null +++ b/applications/plugins/totp/application.fam @@ -0,0 +1,19 @@ +App( + appid="totp", + name="Authenticator", + apptype=FlipperAppType.EXTERNAL, + entry_point="totp_app", + cdefines=["APP_TOTP"], + requires=[ + "gui", + "cli", + "dialogs", + "storage", + "input", + "notification" + ], + stack_size=2 * 1024, + order=20, + fap_category="Misc", + fap_icon="totp_10px.png" +) 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..e3a7f68a6 --- /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..a73a227b5 --- /dev/null +++ b/applications/plugins/totp/scenes/add_new_token/totp_input_text.h @@ -0,0 +1,38 @@ +#pragma once + +#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); 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..e3ec7cb46 --- /dev/null +++ b/applications/plugins/totp/scenes/add_new_token/totp_scene_add_new_token.c @@ -0,0 +1,300 @@ +#include "totp_scene_add_new_token.h" +#include "../../types/common.h" +#include "../../services/ui/constants.h" +#include "../scene_director.h" +#include "totp_input_text.h" +#include "../../types/token_info.h" +#include "../../services/list/list.h" +#include "../../services/base32/base32.h" +#include "../../services/config/config.h" +#include "../../services/ui/ui_controls.h" +#include "../generate_token/totp_scene_generate_token.h" + +#define TOKEN_ALGO_LIST_LENGTH 3 +char* TOKEN_ALGO_LIST[] = {"SHA1", "SHA256", "SHA512"}; +#define TOKEN_DIGITS_LIST_LENGTH 2 +char* TOKEN_DIGITS_LIST[] = {"6 digits", "8 digits"}; + +typedef enum { + TokenNameTextBox, + TokenSecretTextBox, + TokenAlgoSelect, + TokenLengthSelect, + ConfirmButton, +} Control; + +typedef struct { + char* token_name; + uint8_t token_name_length; + char* token_secret; + uint8_t token_secret_length; + bool saved; + Control selected_control; + InputTextSceneContext* token_name_input_context; + InputTextSceneContext* token_secret_input_context; + InputTextSceneState* input_state; + uint32_t input_started_at; + int current_token_index; + int32_t screen_y_offset; + TokenHashAlgo algo; + TokenDigitsCount digits_count; +} SceneState; + +void totp_scene_add_new_token_init(PluginState* plugin_state) { + UNUSED(plugin_state); +} + +static void on_token_name_user_comitted(InputTextSceneCallbackResult* result) { + SceneState* scene_state = result->callback_data; + free(scene_state->token_name); + scene_state->token_name = result->user_input; + scene_state->token_name_length = result->user_input_length; + scene_state->input_started_at = 0; + free(result); +} + +static void on_token_secret_user_comitted(InputTextSceneCallbackResult* result) { + SceneState* scene_state = result->callback_data; + free(scene_state->token_secret); + scene_state->token_secret = result->user_input; + scene_state->token_secret_length = result->user_input_length; + scene_state->input_started_at = 0; + free(result); +} + +void totp_scene_add_new_token_activate( + PluginState* plugin_state, + const TokenAddEditSceneContext* context) { + SceneState* scene_state = malloc(sizeof(SceneState)); + plugin_state->current_scene_state = scene_state; + scene_state->token_name = "Name"; + scene_state->token_name_length = strlen(scene_state->token_name); + scene_state->token_secret = "Secret"; + scene_state->token_secret_length = strlen(scene_state->token_secret); + + scene_state->token_name_input_context = malloc(sizeof(InputTextSceneContext)); + scene_state->token_name_input_context->header_text = "Enter token name"; + scene_state->token_name_input_context->callback_data = scene_state; + scene_state->token_name_input_context->callback = on_token_name_user_comitted; + + scene_state->token_secret_input_context = malloc(sizeof(InputTextSceneContext)); + scene_state->token_secret_input_context->header_text = "Enter token secret"; + scene_state->token_secret_input_context->callback_data = scene_state; + scene_state->token_secret_input_context->callback = on_token_secret_user_comitted; + + scene_state->screen_y_offset = 0; + + scene_state->input_state = NULL; + + if(context == NULL) { + scene_state->current_token_index = -1; + } else { + scene_state->current_token_index = context->current_token_index; + } +} + +void totp_scene_add_new_token_render(Canvas* const canvas, PluginState* plugin_state) { + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + if(scene_state->input_started_at > 0) { + totp_input_text_render(canvas, scene_state->input_state); + return; + } + + ui_control_text_box_render( + canvas, + 10 - scene_state->screen_y_offset, + scene_state->token_name, + scene_state->selected_control == TokenNameTextBox); + ui_control_text_box_render( + canvas, + 27 - scene_state->screen_y_offset, + scene_state->token_secret, + scene_state->selected_control == TokenSecretTextBox); + ui_control_select_render( + canvas, + 44 - scene_state->screen_y_offset, + TOKEN_ALGO_LIST[scene_state->algo], + scene_state->selected_control == TokenAlgoSelect); + ui_control_select_render( + canvas, + 63 - scene_state->screen_y_offset, + TOKEN_DIGITS_LIST[scene_state->digits_count], + scene_state->selected_control == TokenLengthSelect); + ui_control_button_render( + canvas, + SCREEN_WIDTH_CENTER - 24, + 85 - scene_state->screen_y_offset, + 48, + 13, + "Confirm", + scene_state->selected_control == ConfirmButton); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 0, 0, SCREEN_WIDTH, 10); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, "Add new token"); + canvas_set_font(canvas, FontSecondary); +} + +void update_screen_y_offset(SceneState* scene_state) { + if(scene_state->selected_control > TokenAlgoSelect) { + scene_state->screen_y_offset = 35; + } else { + scene_state->screen_y_offset = 0; + } +} + +bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState* plugin_state) { + if(event->type == EventTypeKey) { + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + if(scene_state->input_started_at > 0 && + furi_get_tick() - scene_state->input_started_at > 300) { + return totp_input_text_handle_event(event, scene_state->input_state); + } + + if(event->input.type == InputTypeLong && event->input.key == InputKeyBack) { + return false; + } else if(event->input.type == InputTypePress) { + switch(event->input.key) { + case InputKeyUp: + if(scene_state->selected_control > TokenNameTextBox) { + scene_state->selected_control--; + update_screen_y_offset(scene_state); + } + break; + case InputKeyDown: + if(scene_state->selected_control < ConfirmButton) { + scene_state->selected_control++; + update_screen_y_offset(scene_state); + } + break; + case InputKeyRight: + if(scene_state->selected_control == TokenAlgoSelect) { + if(scene_state->algo < SHA512) { + scene_state->algo++; + } else { + scene_state->algo = SHA1; + } + } else if(scene_state->selected_control == TokenLengthSelect) { + if(scene_state->digits_count < TOTP_8_DIGITS) { + scene_state->digits_count++; + } else { + scene_state->digits_count = TOTP_6_DIGITS; + } + } + break; + case InputKeyLeft: + if(scene_state->selected_control == TokenAlgoSelect) { + if(scene_state->algo > SHA1) { + scene_state->algo--; + } else { + scene_state->algo = SHA512; + } + } else if(scene_state->selected_control == TokenLengthSelect) { + if(scene_state->digits_count > TOTP_6_DIGITS) { + scene_state->digits_count--; + } else { + scene_state->digits_count = TOTP_8_DIGITS; + } + } + break; + case InputKeyOk: + switch(scene_state->selected_control) { + case TokenNameTextBox: + if(scene_state->input_state != NULL) { + totp_input_text_free(scene_state->input_state); + } + scene_state->input_state = + totp_input_text_activate(scene_state->token_name_input_context); + scene_state->input_started_at = furi_get_tick(); + break; + case TokenSecretTextBox: + if(scene_state->input_state != NULL) { + totp_input_text_free(scene_state->input_state); + } + scene_state->input_state = + totp_input_text_activate(scene_state->token_secret_input_context); + scene_state->input_started_at = furi_get_tick(); + break; + case TokenAlgoSelect: + break; + case TokenLengthSelect: + break; + case ConfirmButton: { + TokenInfo* tokenInfo = token_info_alloc(); + tokenInfo->name = malloc(scene_state->token_name_length + 1); + strcpy(tokenInfo->name, scene_state->token_name); + + token_info_set_secret( + tokenInfo, + scene_state->token_secret, + scene_state->token_secret_length, + &plugin_state->iv[0]); + + tokenInfo->algo = scene_state->algo; + tokenInfo->digits = scene_state->digits_count; + + if(plugin_state->tokens_list == NULL) { + plugin_state->tokens_list = list_init_head(tokenInfo); + } else { + list_add(plugin_state->tokens_list, tokenInfo); + } + plugin_state->tokens_count++; + + Storage* cfg_storage = totp_open_storage(); + FlipperFormat* cfg_file = totp_open_config_file(cfg_storage); + + flipper_format_seek_to_end(cfg_file); + totp_config_file_save_new_token(cfg_file, tokenInfo); + + totp_close_config_file(cfg_file); + totp_close_storage(); + + GenerateTokenSceneContext generate_scene_context = { + .current_token_index = plugin_state->tokens_count - 1}; + totp_scene_director_activate_scene( + plugin_state, TotpSceneGenerateToken, &generate_scene_context); + break; + } + } + break; + case InputKeyBack: + if(scene_state->current_token_index >= 0) { + GenerateTokenSceneContext generate_scene_context = { + .current_token_index = scene_state->current_token_index}; + totp_scene_director_activate_scene( + plugin_state, TotpSceneGenerateToken, &generate_scene_context); + } else { + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } + break; + } + } + } + return true; +} + +void totp_scene_add_new_token_deactivate(PluginState* plugin_state) { + if(plugin_state->current_scene_state == NULL) return; + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + free(scene_state->token_name); + free(scene_state->token_secret); + + free(scene_state->token_name_input_context->header_text); + free(scene_state->token_name_input_context); + + free(scene_state->token_secret_input_context->header_text); + free(scene_state->token_secret_input_context); + + if(scene_state->input_state != NULL) { + totp_input_text_free(scene_state->input_state); + } + + free(plugin_state->current_scene_state); + plugin_state->current_scene_state = NULL; +} + +void totp_scene_add_new_token_free(PluginState* plugin_state) { + UNUSED(plugin_state); +} diff --git a/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..b65c567a2 --- /dev/null +++ b/applications/plugins/totp/scenes/add_new_token/totp_scene_add_new_token.h @@ -0,0 +1,20 @@ +#pragma once + +#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); 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..180d7b9a1 --- /dev/null +++ b/applications/plugins/totp/scenes/authenticate/totp_scene_authenticate.c @@ -0,0 +1,203 @@ +#include "totp_scene_authenticate.h" +#include +#include "../../types/common.h" +#include "../../services/ui/icons.h" +#include "../../services/ui/constants.h" +#include "../../services/config/config.h" +#include "../scene_director.h" +#include "../totp_scenes_enum.h" + +#define MAX_CODE_LENGTH TOTP_IV_SIZE +#define CRYPTO_VERIFY_KEY "FFF_Crypto_pass" +#define CRYPTO_VERIFY_KEY_LENGTH 16 + +typedef struct { + uint8_t code_input[MAX_CODE_LENGTH]; + uint8_t code_length; +} SceneState; + +void totp_scene_authenticate_init(PluginState* plugin_state) { + memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE); +} + +void totp_scene_authenticate_activate(PluginState* plugin_state) { + SceneState* scene_state = malloc(sizeof(SceneState)); + scene_state->code_length = 0; + memset(&scene_state->code_input[0], 0, MAX_CODE_LENGTH); + plugin_state->current_scene_state = scene_state; +} + +void totp_scene_authenticate_render(Canvas* const canvas, PluginState* plugin_state) { + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + + int v_shift = 0; + if(scene_state->code_length > 0) { + v_shift = -10; + } + + if(plugin_state->crypto_verify_data == NULL) { + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER - 10 + v_shift, + AlignCenter, + AlignCenter, + "Use arrow keys"); + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER + 5 + v_shift, + AlignCenter, + AlignCenter, + "to setup new PIN"); + } else { + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER + v_shift, + AlignCenter, + AlignCenter, + "Use arrow keys to enter PIN"); + } + const uint8_t PIN_ASTERISK_RADIUS = 3; + const uint8_t PIN_ASTERISK_STEP = (PIN_ASTERISK_RADIUS << 1) + 2; + if(scene_state->code_length > 0) { + uint8_t left_start_x = (scene_state->code_length - 1) * PIN_ASTERISK_STEP >> 1; + for(uint8_t i = 0; i < scene_state->code_length; i++) { + canvas_draw_disc( + canvas, + SCREEN_WIDTH_CENTER - left_start_x + i * PIN_ASTERISK_STEP, + SCREEN_HEIGHT_CENTER + 10, + PIN_ASTERISK_RADIUS); + } + } +} + +bool totp_scene_authenticate_handle_event(PluginEvent* const event, PluginState* plugin_state) { + if(event->type == EventTypeKey) { + if(event->input.type == InputTypeLong && event->input.key == InputKeyBack) { + return false; + } else if(event->input.type == InputTypePress) { + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + + const uint8_t ARROW_UP_CODE = 2; + const uint8_t ARROW_RIGHT_CODE = 8; + const uint8_t ARROW_DOWN_CODE = 11; + const uint8_t ARROW_LEFT_CODE = 5; + + switch(event->input.key) { + case InputKeyUp: + if(scene_state->code_length < MAX_CODE_LENGTH) { + scene_state->code_input[scene_state->code_length] = ARROW_UP_CODE; + scene_state->code_length++; + } + break; + case InputKeyDown: + if(scene_state->code_length < MAX_CODE_LENGTH) { + scene_state->code_input[scene_state->code_length] = ARROW_DOWN_CODE; + scene_state->code_length++; + } + break; + case InputKeyRight: + if(scene_state->code_length < MAX_CODE_LENGTH) { + scene_state->code_input[scene_state->code_length] = ARROW_RIGHT_CODE; + scene_state->code_length++; + } + break; + case InputKeyLeft: + if(scene_state->code_length < MAX_CODE_LENGTH) { + scene_state->code_input[scene_state->code_length] = ARROW_LEFT_CODE; + scene_state->code_length++; + } + break; + case InputKeyOk: + if(plugin_state->crypto_verify_data == NULL) { + FURI_LOG_D(LOGGING_TAG, "Generating new IV"); + furi_hal_random_fill_buf(&plugin_state->base_iv[0], TOTP_IV_SIZE); + } + + memcpy(&plugin_state->iv[0], &plugin_state->base_iv[0], TOTP_IV_SIZE); + for(uint8_t i = 0; i < scene_state->code_length; i++) { + plugin_state->iv[i] = plugin_state->iv[i] ^ + (uint8_t)(scene_state->code_input[i] * (i + 1)); + } + + if(plugin_state->crypto_verify_data == NULL) { + FURI_LOG_D(LOGGING_TAG, "Generating crypto verify data"); + plugin_state->crypto_verify_data = malloc(CRYPTO_VERIFY_KEY_LENGTH); + plugin_state->crypto_verify_data_length = CRYPTO_VERIFY_KEY_LENGTH; + Storage* storage = totp_open_storage(); + FlipperFormat* config_file = totp_open_config_file(storage); + furi_hal_crypto_store_load_key(CRYPTO_KEY_SLOT, &plugin_state->iv[0]); + furi_hal_crypto_encrypt( + (uint8_t*)CRYPTO_VERIFY_KEY, + plugin_state->crypto_verify_data, + CRYPTO_VERIFY_KEY_LENGTH); + furi_hal_crypto_store_unload_key(CRYPTO_KEY_SLOT); + flipper_format_insert_or_update_hex( + config_file, TOTP_CONFIG_KEY_BASE_IV, plugin_state->base_iv, TOTP_IV_SIZE); + flipper_format_insert_or_update_hex( + config_file, + TOTP_CONFIG_KEY_CRYPTO_VERIFY, + plugin_state->crypto_verify_data, + CRYPTO_VERIFY_KEY_LENGTH); + totp_close_config_file(config_file); + totp_close_storage(); + } + + uint8_t decrypted_key[CRYPTO_VERIFY_KEY_LENGTH]; + furi_hal_crypto_store_load_key(CRYPTO_KEY_SLOT, &plugin_state->iv[0]); + furi_hal_crypto_decrypt( + plugin_state->crypto_verify_data, &decrypted_key[0], CRYPTO_VERIFY_KEY_LENGTH); + furi_hal_crypto_store_unload_key(CRYPTO_KEY_SLOT); + + bool key_valid = true; + for(uint8_t i = 0; i < CRYPTO_VERIFY_KEY_LENGTH && key_valid; i++) { + if(decrypted_key[i] != CRYPTO_VERIFY_KEY[i]) key_valid = false; + } + + if(key_valid) { + FURI_LOG_D(LOGGING_TAG, "PIN is valid"); + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL); + } else { + FURI_LOG_D(LOGGING_TAG, "PIN is NOT valid"); + memset(&scene_state->code_input[0], 0, MAX_CODE_LENGTH); + memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE); + scene_state->code_length = 0; + + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, "Try again", NULL, NULL); + dialog_message_set_header( + message, + "You entered\ninvalid PIN", + SCREEN_WIDTH_CENTER - 25, + SCREEN_HEIGHT_CENTER - 5, + AlignCenter, + AlignCenter); + dialog_message_set_icon(message, &I_DolphinCommon_56x48, 72, 17); + dialog_message_show(plugin_state->dialogs, message); + dialog_message_free(message); + } + break; + case InputKeyBack: + if(scene_state->code_length > 0) { + scene_state->code_input[scene_state->code_length - 1] = 0; + scene_state->code_length--; + } + break; + } + } + } + + return true; +} + +void totp_scene_authenticate_deactivate(PluginState* plugin_state) { + if(plugin_state->current_scene_state == NULL) return; + free(plugin_state->current_scene_state); + plugin_state->current_scene_state = NULL; +} + +void totp_scene_authenticate_free(PluginState* plugin_state) { + UNUSED(plugin_state); +} diff --git a/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..f1199a425 --- /dev/null +++ b/applications/plugins/totp/scenes/authenticate/totp_scene_authenticate.h @@ -0,0 +1,14 @@ +#pragma once + +#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); 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..97ef599bd --- /dev/null +++ b/applications/plugins/totp/scenes/generate_token/totp_scene_generate_token.c @@ -0,0 +1,299 @@ +#include +#include +#include +#include "totp_scene_generate_token.h" +#include "../../types/token_info.h" +#include "../../types/common.h" +#include "../../services/ui/icons.h" +#include "../../services/ui/constants.h" +#include "../../services/totp/totp.h" +#include "../../services/config/config.h" +#include "../scene_director.h" +#include "../token_menu/totp_scene_token_menu.h" + +#define TOKEN_LIFETIME 30 +#define DIGIT_TO_CHAR(digit) ((digit) + '0') + +typedef struct { + uint8_t current_token_index; + char last_code[9]; + char* last_code_name; + bool need_token_update; + uint32_t last_token_gen_time; +} SceneState; + +static const NotificationSequence sequence_short_vibro_and_sound = { + &message_display_backlight_on, + &message_green_255, + &message_vibro_on, + &message_note_c5, + &message_delay_50, + &message_vibro_off, + &message_sound_off, + NULL, +}; + +static void i_token_to_str(uint32_t i_token_code, char* str, TokenDigitsCount len) { + if(len == TOTP_8_DIGITS) { + str[8] = '\0'; + } else if(len == TOTP_6_DIGITS) { + str[6] = '\0'; + } + + if(i_token_code == 0) { + if(len > TOTP_6_DIGITS) { + str[7] = '-'; + str[6] = '-'; + } + + str[5] = '-'; + str[4] = '-'; + str[3] = '-'; + str[2] = '-'; + str[1] = '-'; + str[0] = '-'; + } else { + if(len == TOTP_8_DIGITS) { + str[7] = DIGIT_TO_CHAR(i_token_code % 10); + str[6] = DIGIT_TO_CHAR((i_token_code = i_token_code / 10) % 10); + str[5] = DIGIT_TO_CHAR((i_token_code = i_token_code / 10) % 10); + } else if(len == TOTP_6_DIGITS) { + str[5] = DIGIT_TO_CHAR(i_token_code % 10); + } + + str[4] = DIGIT_TO_CHAR((i_token_code = i_token_code / 10) % 10); + str[3] = DIGIT_TO_CHAR((i_token_code = i_token_code / 10) % 10); + str[2] = DIGIT_TO_CHAR((i_token_code = i_token_code / 10) % 10); + str[1] = DIGIT_TO_CHAR((i_token_code = i_token_code / 10) % 10); + str[0] = DIGIT_TO_CHAR((i_token_code = i_token_code / 10) % 10); + } +} + +TOTP_ALGO get_totp_algo_impl(TokenHashAlgo algo) { + switch(algo) { + case SHA1: + return TOTP_ALGO_SHA1; + case SHA256: + return TOTP_ALGO_SHA256; + case SHA512: + return TOTP_ALGO_SHA512; + } + + return NULL; +} + +void update_totp_params(PluginState* const plugin_state) { + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + + if(scene_state->current_token_index < plugin_state->tokens_count) { + TokenInfo* tokenInfo = + (TokenInfo*)(list_element_at( + plugin_state->tokens_list, scene_state->current_token_index) + ->data); + + scene_state->need_token_update = true; + scene_state->last_code_name = tokenInfo->name; + } +} + +void totp_scene_generate_token_init(PluginState* plugin_state) { + UNUSED(plugin_state); +} + +void totp_scene_generate_token_activate( + PluginState* plugin_state, + const GenerateTokenSceneContext* context) { + if(!plugin_state->token_list_loaded) { + totp_config_file_load_tokens(plugin_state); + } + SceneState* scene_state = malloc(sizeof(SceneState)); + if(context == NULL) { + scene_state->current_token_index = 0; + } else { + scene_state->current_token_index = context->current_token_index; + } + scene_state->need_token_update = true; + plugin_state->current_scene_state = scene_state; + FURI_LOG_D(LOGGING_TAG, "Timezone set to: %f", (double)plugin_state->timezone_offset); + update_totp_params(plugin_state); +} + +void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_state) { + if(plugin_state->tokens_count == 0) { + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER - 10, + AlignCenter, + AlignCenter, + "Token list is empty"); + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER + 10, + AlignCenter, + AlignCenter, + "Press OK button to add"); + return; + } + + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + FuriHalRtcDateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt); + + bool is_new_token_time = curr_ts % TOKEN_LIFETIME == 0; + if(is_new_token_time && scene_state->last_token_gen_time != curr_ts) { + scene_state->need_token_update = true; + } + + if(scene_state->need_token_update) { + scene_state->need_token_update = false; + scene_state->last_token_gen_time = curr_ts; + + TokenInfo* tokenInfo = + (TokenInfo*)(list_element_at( + plugin_state->tokens_list, scene_state->current_token_index) + ->data); + + uint8_t* key = malloc(tokenInfo->token_length); + + furi_hal_crypto_store_load_key(CRYPTO_KEY_SLOT, &plugin_state->iv[0]); + furi_hal_crypto_decrypt(tokenInfo->token, key, tokenInfo->token_length); + furi_hal_crypto_store_unload_key(CRYPTO_KEY_SLOT); + + i_token_to_str( + totp_at( + get_totp_algo_impl(tokenInfo->algo), + token_info_get_digits_count(tokenInfo), + key, + tokenInfo->token_length, + curr_ts, + plugin_state->timezone_offset, + TOKEN_LIFETIME), + scene_state->last_code, + tokenInfo->digits); + memset(key, 0, tokenInfo->token_length); + free(key); + + if(is_new_token_time) { + notification_message(plugin_state->notification, &sequence_short_vibro_and_sound); + } + } + + canvas_set_font(canvas, FontPrimary); + uint16_t token_name_width = canvas_string_width(canvas, scene_state->last_code_name); + if(SCREEN_WIDTH - token_name_width > 18) { + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER - 20, + AlignCenter, + AlignCenter, + scene_state->last_code_name); + } else { + canvas_draw_str_aligned( + canvas, + 9, + SCREEN_HEIGHT_CENTER - 20, + AlignLeft, + AlignCenter, + scene_state->last_code_name); + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 0, SCREEN_HEIGHT_CENTER - 24, 9, 9); + canvas_draw_box(canvas, SCREEN_WIDTH - 10, SCREEN_HEIGHT_CENTER - 24, 9, 9); + canvas_set_color(canvas, ColorBlack); + } + + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER, + AlignCenter, + AlignCenter, + scene_state->last_code); + + const uint8_t BAR_MARGIN = 3; + const uint8_t BAR_HEIGHT = 4; + float percentDone = (float)(TOKEN_LIFETIME - curr_ts % TOKEN_LIFETIME) / (float)TOKEN_LIFETIME; + uint8_t barWidth = (uint8_t)((float)(SCREEN_WIDTH - (BAR_MARGIN << 1)) * percentDone); + uint8_t barX = ((SCREEN_WIDTH - (BAR_MARGIN << 1) - barWidth) >> 1) + BAR_MARGIN; + + canvas_draw_box(canvas, barX, SCREEN_HEIGHT - BAR_MARGIN - BAR_HEIGHT, barWidth, BAR_HEIGHT); + + if(plugin_state->tokens_count > 1) { + canvas_draw_xbm( + canvas, + 0, + SCREEN_HEIGHT_CENTER - 24, + ICON_ARROW_LEFT_8x9_WIDTH, + ICON_ARROW_LEFT_8x9_HEIGHT, + &ICON_ARROW_LEFT_8x9[0]); + canvas_draw_xbm( + canvas, + SCREEN_WIDTH - 9, + SCREEN_HEIGHT_CENTER - 24, + ICON_ARROW_RIGHT_8x9_WIDTH, + ICON_ARROW_RIGHT_8x9_HEIGHT, + &ICON_ARROW_RIGHT_8x9[0]); + } +} + +bool totp_scene_generate_token_handle_event(PluginEvent* const event, PluginState* plugin_state) { + if(event->type == EventTypeKey) { + if(event->input.type == InputTypeLong && event->input.key == InputKeyBack) { + return false; + } else if(event->input.type == InputTypePress) { + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + switch(event->input.key) { + case InputKeyUp: + break; + case InputKeyDown: + break; + case InputKeyRight: + if(scene_state->current_token_index < plugin_state->tokens_count - 1) { + scene_state->current_token_index++; + } else { + scene_state->current_token_index = 0; + } + update_totp_params(plugin_state); + break; + case InputKeyLeft: + if(scene_state->current_token_index > 0) { + scene_state->current_token_index--; + } else { + scene_state->current_token_index = plugin_state->tokens_count - 1; + } + update_totp_params(plugin_state); + break; + case InputKeyOk: + if(plugin_state->tokens_count == 0) { + totp_scene_director_activate_scene(plugin_state, TotpSceneAddNewToken, NULL); + } else { + TokenMenuSceneContext ctx = { + .current_token_index = scene_state->current_token_index}; + totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, &ctx); + } + break; + case InputKeyBack: + break; + } + } + } + + return true; +} + +void totp_scene_generate_token_deactivate(PluginState* plugin_state) { + if(plugin_state->current_scene_state == NULL) return; + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + + free(scene_state->last_code); + free(scene_state); + plugin_state->current_scene_state = NULL; +} + +void totp_scene_generate_token_free(PluginState* plugin_state) { + UNUSED(plugin_state); +} diff --git a/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..1284c7b41 --- /dev/null +++ b/applications/plugins/totp/scenes/generate_token/totp_scene_generate_token.h @@ -0,0 +1,20 @@ +#pragma once + +#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); diff --git a/applications/plugins/totp/scenes/scene_director.c b/applications/plugins/totp/scenes/scene_director.c new file mode 100644 index 000000000..37cbe28c6 --- /dev/null +++ b/applications/plugins/totp/scenes/scene_director.c @@ -0,0 +1,99 @@ +#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..3c25afff6 --- /dev/null +++ b/applications/plugins/totp/scenes/scene_director.h @@ -0,0 +1,16 @@ +#pragma once + +#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); 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..6e1cd5439 --- /dev/null +++ b/applications/plugins/totp/scenes/token_menu/totp_scene_token_menu.c @@ -0,0 +1,139 @@ +#include "totp_scene_token_menu.h" +#include +#include +#include "../../services/ui/ui_controls.h" +#include "../../services/ui/constants.h" +#include "../scene_director.h" +#include "../../services/config/config.h" +#include "../../services/list/list.h" +#include "../../types/token_info.h" +#include "../generate_token/totp_scene_generate_token.h" +#include "../add_new_token/totp_scene_add_new_token.h" + +typedef enum { AddNewToken, DeleteToken } Control; + +typedef struct { + Control selected_control; + uint8_t current_token_index; +} SceneState; + +void totp_scene_token_menu_init(PluginState* plugin_state) { + UNUSED(plugin_state); +} + +void totp_scene_token_menu_activate( + PluginState* plugin_state, + const TokenMenuSceneContext* context) { + SceneState* scene_state = malloc(sizeof(SceneState)); + plugin_state->current_scene_state = scene_state; + scene_state->current_token_index = context->current_token_index; +} + +void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_state) { + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + ui_control_button_render( + canvas, + SCREEN_WIDTH_CENTER - 36, + 5, + 72, + 21, + "Add new token", + scene_state->selected_control == AddNewToken); + ui_control_button_render( + canvas, + SCREEN_WIDTH_CENTER - 36, + 39, + 72, + 21, + "Delete token", + scene_state->selected_control == DeleteToken); +} + +bool totp_scene_token_menu_handle_event(PluginEvent* const event, PluginState* plugin_state) { + if(event->type == EventTypeKey) { + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + if(event->input.type == InputTypePress) { + switch(event->input.key) { + case InputKeyUp: + if(scene_state->selected_control > AddNewToken) { + scene_state->selected_control--; + } + break; + case InputKeyDown: + if(scene_state->selected_control < DeleteToken) { + scene_state->selected_control++; + } + break; + case InputKeyRight: + break; + case InputKeyLeft: + break; + case InputKeyOk: + switch(scene_state->selected_control) { + case AddNewToken: { + TokenAddEditSceneContext add_new_token_scene_context = { + .current_token_index = scene_state->current_token_index}; + totp_scene_director_activate_scene( + plugin_state, TotpSceneAddNewToken, &add_new_token_scene_context); + break; + } + case DeleteToken: { + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, "No", NULL, "Yes"); + dialog_message_set_header(message, "Confirmation", 0, 0, AlignLeft, AlignTop); + dialog_message_set_text( + message, + "Are you sure want to delete?", + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER, + AlignCenter, + AlignCenter); + DialogMessageButton dialog_result = + dialog_message_show(plugin_state->dialogs, message); + dialog_message_free(message); + if(dialog_result == DialogMessageButtonRight) { + uint8_t i = 0; + + ListNode* list_node = plugin_state->tokens_list; + while(i < scene_state->current_token_index && list_node->next != NULL) { + list_node = list_node->next; + i++; + } + + TokenInfo* tokenInfo = list_node->data; + token_info_free(tokenInfo); + plugin_state->tokens_list = + list_remove(plugin_state->tokens_list, list_node); + plugin_state->tokens_count--; + + totp_full_save_config_file(plugin_state); + totp_scene_director_activate_scene( + plugin_state, TotpSceneGenerateToken, NULL); + } + break; + } + } + break; + case InputKeyBack: { + GenerateTokenSceneContext generate_scene_context = { + .current_token_index = scene_state->current_token_index}; + totp_scene_director_activate_scene( + plugin_state, TotpSceneGenerateToken, &generate_scene_context); + break; + } + } + } + } + return true; +} + +void totp_scene_token_menu_deactivate(PluginState* plugin_state) { + if(plugin_state->current_scene_state == NULL) return; + + free(plugin_state->current_scene_state); + plugin_state->current_scene_state = NULL; +} + +void totp_scene_token_menu_free(PluginState* plugin_state) { + UNUSED(plugin_state); +} diff --git a/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..0b117cb25 --- /dev/null +++ b/applications/plugins/totp/scenes/token_menu/totp_scene_token_menu.h @@ -0,0 +1,20 @@ +#pragma once + +#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); 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..32cfcb238 --- /dev/null +++ b/applications/plugins/totp/scenes/totp_scenes_enum.h @@ -0,0 +1,8 @@ +#pragma once + +typedef enum { + TotpSceneAuthentication, + TotpSceneGenerateToken, + TotpSceneAddNewToken, + TotpSceneTokenMenu +} Scene; diff --git a/applications/plugins/totp/services/base32/base32.c b/applications/plugins/totp/services/base32/base32.c new file mode 100644 index 000000000..ce1a16720 --- /dev/null +++ b/applications/plugins/totp/services/base32/base32.c @@ -0,0 +1,94 @@ +// 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/services/base32/base32.h b/applications/plugins/totp/services/base32/base32.h new file mode 100644 index 000000000..db235195c --- /dev/null +++ b/applications/plugins/totp/services/base32/base32.h @@ -0,0 +1,35 @@ +// 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. + +#pragma once + +#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"))); diff --git a/applications/plugins/totp/services/config/config.c b/applications/plugins/totp/services/config/config.c new file mode 100644 index 000000000..44e6b4bb7 --- /dev/null +++ b/applications/plugins/totp/services/config/config.c @@ -0,0 +1,381 @@ +#include "config.h" +#include +#include +#include "../list/list.h" +#include "../../types/common.h" +#include "../../types/token_info.h" +#include "migrations/config_migration_v1_to_v2.h" + +#define CONFIG_FILE_DIRECTORY_PATH "/ext/apps/Misc" +#define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/totp.conf" +#define CONFIG_FILE_BACKUP_PATH CONFIG_FILE_PATH ".backup" + +uint8_t token_info_get_digits_as_int(TokenInfo* token_info) { + switch(token_info->digits) { + case TOTP_6_DIGITS: + return 6; + case TOTP_8_DIGITS: + return 8; + } + + return 6; +} + +void token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits) { + switch(digits) { + case 6: + token_info->digits = TOTP_6_DIGITS; + break; + case 8: + token_info->digits = TOTP_8_DIGITS; + break; + } +} + +char* token_info_get_algo_as_cstr(TokenInfo* token_info) { + switch(token_info->algo) { + case SHA1: + return TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME; + case SHA256: + return TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME; + case SHA512: + return TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME; + } + + return NULL; +} + +void token_info_set_algo_from_str(TokenInfo* token_info, FuriString* str) { + if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME) == 0) { + token_info->algo = SHA1; + } else if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME)) { + token_info->algo = SHA256; + } else if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME)) { + token_info->algo = SHA512; + } +} + +Storage* totp_open_storage() { + return furi_record_open(RECORD_STORAGE); +} + +void totp_close_storage() { + furi_record_close(RECORD_STORAGE); +} + +FlipperFormat* totp_open_config_file(Storage* storage) { + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + + if(storage_common_stat(storage, CONFIG_FILE_PATH, NULL) == FSE_OK) { + FURI_LOG_D(LOGGING_TAG, "Config file %s found", CONFIG_FILE_PATH); + if(!flipper_format_file_open_existing(fff_data_file, CONFIG_FILE_PATH)) { + FURI_LOG_E(LOGGING_TAG, "Error opening existing file %s", CONFIG_FILE_PATH); + totp_close_config_file(fff_data_file); + return NULL; + } + } else { + FURI_LOG_D(LOGGING_TAG, "Config file %s is not found. Will create new.", CONFIG_FILE_PATH); + if(storage_common_stat(storage, CONFIG_FILE_DIRECTORY_PATH, NULL) == FSE_NOT_EXIST) { + FURI_LOG_D( + LOGGING_TAG, + "Directory %s doesn't exist. Will create new.", + CONFIG_FILE_DIRECTORY_PATH); + if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) { + FURI_LOG_E(LOGGING_TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH); + return NULL; + } + } + + if(!flipper_format_file_open_new(fff_data_file, CONFIG_FILE_PATH)) { + totp_close_config_file(fff_data_file); + FURI_LOG_E(LOGGING_TAG, "Error creating new file %s", CONFIG_FILE_PATH); + return NULL; + } + + flipper_format_write_header_cstr( + fff_data_file, CONFIG_FILE_HEADER, CONFIG_FILE_ACTUAL_VERSION); + float tmp_tz = 0; + flipper_format_write_comment_cstr(fff_data_file, " "); + flipper_format_write_comment_cstr( + fff_data_file, + "Timezone offset in hours. Important note: do not put '+' sign for positive values"); + flipper_format_write_float(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, &tmp_tz, 1); + FuriString* temp_str = furi_string_alloc(); + + flipper_format_write_comment_cstr(fff_data_file, " "); + flipper_format_write_comment_cstr(fff_data_file, "=== TOKEN SAMPLE BEGIN ==="); + flipper_format_write_comment_cstr(fff_data_file, " "); + flipper_format_write_comment_cstr( + fff_data_file, "# Token name which will be visible in the UI."); + furi_string_printf(temp_str, "%s: Sample token name", TOTP_CONFIG_KEY_TOKEN_NAME); + flipper_format_write_comment(fff_data_file, temp_str); + flipper_format_write_comment_cstr(fff_data_file, " "); + + flipper_format_write_comment_cstr( + fff_data_file, + "# Plain token secret without spaces, dashes and etc, just pure alpha-numeric characters. Important note: plain token will be encrypted and replaced by TOTP app"); + furi_string_printf(temp_str, "%s: plaintokensecret", TOTP_CONFIG_KEY_TOKEN_SECRET); + flipper_format_write_comment(fff_data_file, temp_str); + flipper_format_write_comment_cstr(fff_data_file, " "); + + furi_string_printf( + temp_str, + " # Token hashing algorithm to use during code generation. Supported options are %s, %s and %s. If you are not use which one to use - use %s", + TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME, + TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME, + TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME, + TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME); + flipper_format_write_comment(fff_data_file, temp_str); + furi_string_printf( + temp_str, "%s: %s", TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME); + flipper_format_write_comment(fff_data_file, temp_str); + flipper_format_write_comment_cstr(fff_data_file, " "); + + flipper_format_write_comment_cstr( + fff_data_file, + "# How many digits there should be in generated code. Available options are 6 and 8. Majority websites requires 6 digits code, however some rare websites wants to get 8 digits code. If you are not sure which one to use - use 6"); + furi_string_printf(temp_str, "%s: 6", TOTP_CONFIG_KEY_TOKEN_DIGITS); + flipper_format_write_comment(fff_data_file, temp_str); + flipper_format_write_comment_cstr(fff_data_file, " "); + + flipper_format_write_comment_cstr(fff_data_file, "=== TOKEN SAMPLE END ==="); + flipper_format_write_comment_cstr(fff_data_file, " "); + + furi_string_free(temp_str); + if(!flipper_format_rewind(fff_data_file)) { + totp_close_config_file(fff_data_file); + FURI_LOG_E(LOGGING_TAG, "Rewind error"); + return NULL; + } + } + + return fff_data_file; +} + +void totp_config_file_save_new_token(FlipperFormat* file, TokenInfo* token_info) { + flipper_format_write_string_cstr(file, TOTP_CONFIG_KEY_TOKEN_NAME, token_info->name); + flipper_format_write_hex( + file, TOTP_CONFIG_KEY_TOKEN_SECRET, token_info->token, token_info->token_length); + flipper_format_write_string_cstr( + file, TOTP_CONFIG_KEY_TOKEN_ALGO, token_info_get_algo_as_cstr(token_info)); + uint32_t digits_count_as_uint32 = token_info_get_digits_as_int(token_info); + flipper_format_write_uint32(file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &digits_count_as_uint32, 1); +} + +void totp_full_save_config_file(PluginState* const plugin_state) { + Storage* storage = totp_open_storage(); + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + + flipper_format_file_open_always(fff_data_file, CONFIG_FILE_PATH); + flipper_format_write_header_cstr( + fff_data_file, CONFIG_FILE_HEADER, CONFIG_FILE_ACTUAL_VERSION); + flipper_format_write_hex( + fff_data_file, TOTP_CONFIG_KEY_BASE_IV, &plugin_state->base_iv[0], TOTP_IV_SIZE); + flipper_format_write_hex( + fff_data_file, + TOTP_CONFIG_KEY_CRYPTO_VERIFY, + plugin_state->crypto_verify_data, + plugin_state->crypto_verify_data_length); + flipper_format_write_float( + fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1); + ListNode* node = plugin_state->tokens_list; + while(node != NULL) { + TokenInfo* token_info = node->data; + totp_config_file_save_new_token(fff_data_file, token_info); + node = node->next; + } + + totp_close_config_file(fff_data_file); + totp_close_storage(); +} + +void totp_config_file_load_base(PluginState* const plugin_state) { + Storage* storage = totp_open_storage(); + FlipperFormat* fff_data_file = totp_open_config_file(storage); + + plugin_state->timezone_offset = 0; + + FuriString* temp_str = furi_string_alloc(); + + uint32_t file_version; + if(!flipper_format_read_header(fff_data_file, temp_str, &file_version)) { + FURI_LOG_E(LOGGING_TAG, "Missing or incorrect header"); + furi_string_free(temp_str); + return; + } + + if(file_version < CONFIG_FILE_ACTUAL_VERSION) { + FURI_LOG_I( + LOGGING_TAG, + "Obsolete config file version detected. Current version: %ld; Actual version: %d", + file_version, + CONFIG_FILE_ACTUAL_VERSION); + totp_close_config_file(fff_data_file); + + if(storage_common_stat(storage, CONFIG_FILE_BACKUP_PATH, NULL) == FSE_OK) { + storage_simply_remove(storage, CONFIG_FILE_BACKUP_PATH); + } + + if(storage_common_copy(storage, CONFIG_FILE_PATH, CONFIG_FILE_BACKUP_PATH) == FSE_OK) { + FURI_LOG_I(LOGGING_TAG, "Took config file backup to %s", CONFIG_FILE_BACKUP_PATH); + fff_data_file = totp_open_config_file(storage); + FlipperFormat* fff_backup_data_file = flipper_format_file_alloc(storage); + flipper_format_file_open_existing(fff_backup_data_file, CONFIG_FILE_BACKUP_PATH); + + if(file_version == 1) { + if(totp_config_migrate_v1_to_v2(fff_data_file, fff_backup_data_file)) { + FURI_LOG_I(LOGGING_TAG, "Applied migration from v1 to v2"); + } else { + FURI_LOG_W(LOGGING_TAG, "An error occurred during migration from v1 to v2"); + } + } + + flipper_format_file_close(fff_backup_data_file); + flipper_format_free(fff_backup_data_file); + flipper_format_rewind(fff_data_file); + } else { + FURI_LOG_E( + LOGGING_TAG, + "An error occurred during taking backup of %s into %s before migration", + CONFIG_FILE_PATH, + CONFIG_FILE_BACKUP_PATH); + } + } + + if(!flipper_format_read_hex( + fff_data_file, TOTP_CONFIG_KEY_BASE_IV, &plugin_state->base_iv[0], TOTP_IV_SIZE)) { + FURI_LOG_D(LOGGING_TAG, "Missing base IV"); + } + + flipper_format_rewind(fff_data_file); + + uint32_t crypto_size; + if(flipper_format_get_value_count(fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, &crypto_size)) { + plugin_state->crypto_verify_data = malloc(sizeof(uint8_t) * crypto_size); + plugin_state->crypto_verify_data_length = crypto_size; + if(!flipper_format_read_hex( + fff_data_file, + TOTP_CONFIG_KEY_CRYPTO_VERIFY, + plugin_state->crypto_verify_data, + crypto_size)) { + FURI_LOG_D(LOGGING_TAG, "Missing crypto verify token"); + free(plugin_state->crypto_verify_data); + plugin_state->crypto_verify_data = NULL; + } + } + + flipper_format_rewind(fff_data_file); + + if(!flipper_format_read_float( + fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1)) { + plugin_state->timezone_offset = 0; + FURI_LOG_D(LOGGING_TAG, "Missing timezone offset information, defaulting to 0"); + } + + furi_string_free(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); + + FuriString* temp_str = furi_string_alloc(); + uint32_t temp_data32; + + if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) { + FURI_LOG_E(LOGGING_TAG, "Missing or incorrect header"); + furi_string_free(temp_str); + return; + } + + uint8_t index = 0; + bool has_any_plain_secret = false; + + while(true) { + if(!flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str)) { + break; + } + + TokenInfo* tokenInfo = token_info_alloc(); + + const char* temp_cstr = furi_string_get_cstr(temp_str); + tokenInfo->name = (char*)malloc(strlen(temp_cstr) + 1); + strcpy(tokenInfo->name, temp_cstr); + + uint32_t secret_bytes_count; + if(!flipper_format_get_value_count( + fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, &secret_bytes_count)) { + token_info_free(tokenInfo); + continue; + } + + if(secret_bytes_count == 1) { // Plain secret key + if(!flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str)) { + token_info_free(tokenInfo); + continue; + } + + temp_cstr = furi_string_get_cstr(temp_str); + token_info_set_secret(tokenInfo, temp_cstr, strlen(temp_cstr), &plugin_state->iv[0]); + has_any_plain_secret = true; + FURI_LOG_W(LOGGING_TAG, "Found token with plain secret"); + } else { // encrypted + tokenInfo->token_length = secret_bytes_count; + tokenInfo->token = malloc(tokenInfo->token_length); + if(!flipper_format_read_hex( + fff_data_file, + TOTP_CONFIG_KEY_TOKEN_SECRET, + tokenInfo->token, + tokenInfo->token_length)) { + token_info_free(tokenInfo); + continue; + } + } + + if(!flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str)) { + token_info_free(tokenInfo); + continue; + } + + token_info_set_algo_from_str(tokenInfo, temp_str); + + if(!flipper_format_read_uint32( + fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &temp_data32, 1)) { + token_info_free(tokenInfo); + continue; + } + + token_info_set_digits_from_int(tokenInfo, temp_data32); + + FURI_LOG_D(LOGGING_TAG, "Found token \"%s\"", tokenInfo->name); + + if(plugin_state->tokens_list == NULL) { + plugin_state->tokens_list = list_init_head(tokenInfo); + } else { + list_add(plugin_state->tokens_list, tokenInfo); + } + + index++; + } + + plugin_state->tokens_count = index; + plugin_state->token_list_loaded = true; + + FURI_LOG_D(LOGGING_TAG, "Found %d tokens", index); + + furi_string_free(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/services/config/config.h b/applications/plugins/totp/services/config/config.h new file mode 100644 index 000000000..62ad0b44d --- /dev/null +++ b/applications/plugins/totp/services/config/config.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include "../../types/plugin_state.h" +#include "../../types/token_info.h" +#include "constants.h" + +Storage* totp_open_storage(); +void totp_close_storage(); +FlipperFormat* totp_open_config_file(Storage* storage); +void totp_close_config_file(FlipperFormat* file); +void totp_full_save_config_file(PluginState* const plugin_state); +void totp_config_file_load_base(PluginState* const plugin_state); +void totp_config_file_load_tokens(PluginState* const plugin_state); +void totp_config_file_save_new_token(FlipperFormat* file, TokenInfo* token_info); diff --git a/applications/plugins/totp/services/config/constants.h b/applications/plugins/totp/services/config/constants.h new file mode 100644 index 000000000..931db4e02 --- /dev/null +++ b/applications/plugins/totp/services/config/constants.h @@ -0,0 +1,16 @@ +#pragma once + +#define CONFIG_FILE_HEADER "Flipper TOTP plugin config file" +#define CONFIG_FILE_ACTUAL_VERSION 2 + +#define TOTP_CONFIG_KEY_TIMEZONE "Timezone" +#define TOTP_CONFIG_KEY_TOKEN_NAME "TokenName" +#define TOTP_CONFIG_KEY_TOKEN_SECRET "TokenSecret" +#define TOTP_CONFIG_KEY_TOKEN_ALGO "TokenAlgo" +#define TOTP_CONFIG_KEY_TOKEN_DIGITS "TokenDigits" +#define TOTP_CONFIG_KEY_CRYPTO_VERIFY "Crypto" +#define TOTP_CONFIG_KEY_BASE_IV "BaseIV" + +#define TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME "sha1" +#define TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME "sha256" +#define TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME "sha512" diff --git a/applications/plugins/totp/services/config/migrations/config_migration_v1_to_v2.c b/applications/plugins/totp/services/config/migrations/config_migration_v1_to_v2.c new file mode 100644 index 000000000..1ed8c37f0 --- /dev/null +++ b/applications/plugins/totp/services/config/migrations/config_migration_v1_to_v2.c @@ -0,0 +1,46 @@ +#include "config_migration_v1_to_v2.h" +#include +#include "../constants.h" + +#define NEW_VERSION 2 + +bool totp_config_migrate_v1_to_v2( + FlipperFormat* fff_data_file, + FlipperFormat* fff_backup_data_file) { + flipper_format_write_header_cstr(fff_data_file, CONFIG_FILE_HEADER, NEW_VERSION); + + FuriString* temp_str = furi_string_alloc(); + + if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str)) { + flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str); + } + + if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str)) { + flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str); + } + + if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str)) { + flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str); + } + + while(true) { + if(!flipper_format_read_string( + fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str)) { + break; + } + + flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str); + + flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str); + flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str); + + flipper_format_write_string_cstr( + fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME); + uint32_t default_digits = 6; + flipper_format_write_uint32( + fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &default_digits, 1); + } + + furi_string_free(temp_str); + return true; +} diff --git a/applications/plugins/totp/services/config/migrations/config_migration_v1_to_v2.h b/applications/plugins/totp/services/config/migrations/config_migration_v1_to_v2.h new file mode 100644 index 000000000..99470f04d --- /dev/null +++ b/applications/plugins/totp/services/config/migrations/config_migration_v1_to_v2.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +bool totp_config_migrate_v1_to_v2( + FlipperFormat* fff_data_file, + FlipperFormat* fff_backup_data_file); diff --git a/applications/plugins/totp/services/hmac/byteswap.c b/applications/plugins/totp/services/hmac/byteswap.c new file mode 100644 index 000000000..e922deec0 --- /dev/null +++ b/applications/plugins/totp/services/hmac/byteswap.c @@ -0,0 +1,12 @@ +#include "byteswap.h" + +uint32_t swap_uint32(uint32_t val) { + val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF); + return (val << 16) | (val >> 16); +} + +uint64_t swap_uint64(uint64_t val) { + val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL); + val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL); + return (val << 32) | (val >> 32); +} diff --git a/applications/plugins/totp/services/hmac/byteswap.h b/applications/plugins/totp/services/hmac/byteswap.h new file mode 100644 index 000000000..411f3d5c4 --- /dev/null +++ b/applications/plugins/totp/services/hmac/byteswap.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +uint32_t swap_uint32(uint32_t val); +uint64_t swap_uint64(uint64_t val); diff --git a/applications/plugins/totp/services/hmac/hmac_common.h b/applications/plugins/totp/services/hmac/hmac_common.h new file mode 100644 index 000000000..9c5b5828f --- /dev/null +++ b/applications/plugins/totp/services/hmac/hmac_common.h @@ -0,0 +1,64 @@ +#include +#include "sha256.h" +#include "memxor.h" + +#define IPAD 0x36 +#define OPAD 0x5c + +/* Concatenate two preprocessor tokens. */ +#define _GLHMAC_CONCAT_(prefix, suffix) prefix##suffix +#define _GLHMAC_CONCAT(prefix, suffix) _GLHMAC_CONCAT_(prefix, suffix) + +#if GL_HMAC_NAME == 5 +#define HMAC_ALG md5 +#else +#define HMAC_ALG _GLHMAC_CONCAT(sha, GL_HMAC_NAME) +#endif + +#define GL_HMAC_CTX _GLHMAC_CONCAT(HMAC_ALG, _ctx) +#define GL_HMAC_FN _GLHMAC_CONCAT(hmac_, HMAC_ALG) +#define GL_HMAC_FN_INIT _GLHMAC_CONCAT(HMAC_ALG, _init_ctx) +#define GL_HMAC_FN_BLOC _GLHMAC_CONCAT(HMAC_ALG, _process_block) +#define GL_HMAC_FN_PROC _GLHMAC_CONCAT(HMAC_ALG, _process_bytes) +#define GL_HMAC_FN_FINI _GLHMAC_CONCAT(HMAC_ALG, _finish_ctx) + +static void + hmac_hash(const void* key, size_t keylen, const void* in, size_t inlen, int pad, void* resbuf) { + struct GL_HMAC_CTX hmac_ctx; + char block[GL_HMAC_BLOCKSIZE]; + + memset(block, pad, sizeof block); + memxor(block, key, keylen); + + GL_HMAC_FN_INIT(&hmac_ctx); + GL_HMAC_FN_BLOC(block, sizeof block, &hmac_ctx); + GL_HMAC_FN_PROC(in, inlen, &hmac_ctx); + GL_HMAC_FN_FINI(&hmac_ctx, resbuf); +} + +int GL_HMAC_FN(const void* key, size_t keylen, const void* in, size_t inlen, void* resbuf) { + char optkeybuf[GL_HMAC_HASHSIZE]; + char innerhash[GL_HMAC_HASHSIZE]; + + /* Ensure key size is <= block size. */ + if(keylen > GL_HMAC_BLOCKSIZE) { + struct GL_HMAC_CTX keyhash; + + GL_HMAC_FN_INIT(&keyhash); + GL_HMAC_FN_PROC(key, keylen, &keyhash); + GL_HMAC_FN_FINI(&keyhash, optkeybuf); + + key = optkeybuf; + /* zero padding of the key to the block size + is implicit in the memxor. */ + keylen = sizeof optkeybuf; + } + + /* Compute INNERHASH from KEY and IN. */ + hmac_hash(key, keylen, in, inlen, IPAD, innerhash); + + /* Compute result from KEY and INNERHASH. */ + hmac_hash(key, keylen, innerhash, sizeof innerhash, OPAD, resbuf); + + return 0; +} diff --git a/applications/plugins/totp/services/hmac/hmac_sha1.c b/applications/plugins/totp/services/hmac/hmac_sha1.c new file mode 100644 index 000000000..a597ecb3a --- /dev/null +++ b/applications/plugins/totp/services/hmac/hmac_sha1.c @@ -0,0 +1,24 @@ +/* hmac_sha1.c -- hashed message authentication codes + Copyright (C) 2018-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#include "hmac_sha1.h" + +#include "sha1.h" + +#define GL_HMAC_NAME 1 +#define GL_HMAC_BLOCKSIZE 64 +#define GL_HMAC_HASHSIZE 20 +#include "hmac_common.h" diff --git a/applications/plugins/totp/services/hmac/hmac_sha1.h b/applications/plugins/totp/services/hmac/hmac_sha1.h new file mode 100644 index 000000000..25ff2f648 --- /dev/null +++ b/applications/plugins/totp/services/hmac/hmac_sha1.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#define HMAC_SHA1_RESULT_SIZE 20 + +/* Compute Hashed Message Authentication Code with SHA-1, over BUFFER + data of BUFLEN bytes using the KEY of KEYLEN bytes, writing the + output to pre-allocated 20 byte minimum RESBUF buffer. Return 0 on + success. */ +int hmac_sha1(const void* key, size_t keylen, const void* in, size_t inlen, void* restrict resbuf); diff --git a/applications/plugins/totp/services/hmac/hmac_sha256.c b/applications/plugins/totp/services/hmac/hmac_sha256.c new file mode 100644 index 000000000..dbf027101 --- /dev/null +++ b/applications/plugins/totp/services/hmac/hmac_sha256.c @@ -0,0 +1,23 @@ +/* hmac_sha256.c -- hashed message authentication codes + Copyright (C) 2018-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#include "hmac_sha256.h" + +#define GL_HMAC_NAME 256 +#define GL_HMAC_BLOCKSIZE 64 +#define GL_HMAC_HASHSIZE 32 + +#include "hmac_common.h" diff --git a/applications/plugins/totp/services/hmac/hmac_sha256.h b/applications/plugins/totp/services/hmac/hmac_sha256.h new file mode 100644 index 000000000..9aeaf10d6 --- /dev/null +++ b/applications/plugins/totp/services/hmac/hmac_sha256.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#define HMAC_SHA256_RESULT_SIZE 32 + +/* Compute Hashed Message Authentication Code with SHA-256, over BUFFER + data of BUFLEN bytes using the KEY of KEYLEN bytes, writing the + output to pre-allocated 32 byte minimum RESBUF buffer. Return 0 on + success. */ +int hmac_sha256(const void* key, size_t keylen, const void* in, size_t inlen, void* restrict resbuf); diff --git a/applications/plugins/totp/services/hmac/hmac_sha512.c b/applications/plugins/totp/services/hmac/hmac_sha512.c new file mode 100644 index 000000000..eafe80fd2 --- /dev/null +++ b/applications/plugins/totp/services/hmac/hmac_sha512.c @@ -0,0 +1,24 @@ +/* hmac_sha512.c -- hashed message authentication codes + Copyright (C) 2018-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#include "hmac_sha512.h" + +#include "sha512.h" + +#define GL_HMAC_NAME 512 +#define GL_HMAC_BLOCKSIZE 128 +#define GL_HMAC_HASHSIZE 64 +#include "hmac_common.h" diff --git a/applications/plugins/totp/services/hmac/hmac_sha512.h b/applications/plugins/totp/services/hmac/hmac_sha512.h new file mode 100644 index 000000000..712b7f4a0 --- /dev/null +++ b/applications/plugins/totp/services/hmac/hmac_sha512.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#define HMAC_SHA512_RESULT_SIZE 64 + +/* Compute Hashed Message Authentication Code with SHA-512, over BUFFER + data of BUFLEN bytes using the KEY of KEYLEN bytes, writing the + output to pre-allocated 64 byte minimum RESBUF buffer. Return 0 on + success. */ +int hmac_sha512(const void* key, size_t keylen, const void* in, size_t inlen, void* restrict resbuf); diff --git a/applications/plugins/totp/services/hmac/memxor.c b/applications/plugins/totp/services/hmac/memxor.c new file mode 100644 index 000000000..6824ea33b --- /dev/null +++ b/applications/plugins/totp/services/hmac/memxor.c @@ -0,0 +1,32 @@ +/* memxor.c -- perform binary exclusive OR operation of two memory blocks. + Copyright (C) 2005, 2006 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Simon Josefsson. The interface was inspired by memxor + in Niels Möller's Nettle. */ + +/* #include */ + +#include "memxor.h" + +void* memxor(void* /*restrict*/ dest, const void* /*restrict*/ src, size_t n) { + char const* s = (char const*)src; + char* d = (char*)dest; + + for(; n > 0; n--) *d++ ^= *s++; + + return dest; +} diff --git a/applications/plugins/totp/services/hmac/memxor.h b/applications/plugins/totp/services/hmac/memxor.h new file mode 100644 index 000000000..71aa604c8 --- /dev/null +++ b/applications/plugins/totp/services/hmac/memxor.h @@ -0,0 +1,28 @@ +/* memxor.h -- perform binary exclusive OR operation on memory blocks. + Copyright (C) 2005 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/* Written by Simon Josefsson. The interface was inspired by memxor + in Niels Möller's Nettle. */ + +#pragma once + +#include + +/* Compute binary exclusive OR of memory areas DEST and SRC, putting + the result in DEST, of length N bytes. Returns a pointer to + DEST. */ +void* memxor(void* /*restrict*/ dest, const void* /*restrict*/ src, size_t n); diff --git a/applications/plugins/totp/services/hmac/sha1.c b/applications/plugins/totp/services/hmac/sha1.c new file mode 100644 index 000000000..5679e489d --- /dev/null +++ b/applications/plugins/totp/services/hmac/sha1.c @@ -0,0 +1,331 @@ +/* sha1.c - Functions to compute SHA1 message digest of files or + memory blocks according to the NIST specification FIPS-180-1. + + Copyright (C) 2000-2001, 2003-2006, 2008-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +/* Written by Scott G. Miller + Credits: + Robert Klep -- Expansion function fix +*/ + +/* Specification. */ +#if HAVE_OPENSSL_SHA1 +#define GL_OPENSSL_INLINE _GL_EXTERN_INLINE +#endif +#include "sha1.h" + +#include +#include + +#ifdef WORDS_BIGENDIAN +#define SWAP(n) (n) +#else +#include "byteswap.h" +#define SWAP(n) swap_uint32(n) +#endif + +#if !HAVE_OPENSSL_SHA1 + +/* This array contains the bytes used to pad the buffer to the next + 64-byte boundary. (RFC 1321, 3.1: Step 1) */ +static const unsigned char fillbuf[64] = {0x80, 0 /* , 0, 0, ... */}; + +/* Take a pointer to a 160 bit block of data (five 32 bit ints) and + initialize it to the start constants of the SHA1 algorithm. This + must be called before using hash in the call to sha1_hash. */ +void sha1_init_ctx(struct sha1_ctx* ctx) { + ctx->A = 0x67452301; + ctx->B = 0xefcdab89; + ctx->C = 0x98badcfe; + ctx->D = 0x10325476; + ctx->E = 0xc3d2e1f0; + + ctx->total[0] = ctx->total[1] = 0; + ctx->buflen = 0; +} + +/* Copy the 4 byte value from v into the memory location pointed to by *cp, + If your architecture allows unaligned access this is equivalent to + * (uint32_t *) cp = v */ +static void set_uint32(char* cp, uint32_t v) { + memcpy(cp, &v, sizeof v); +} + +/* Put result from CTX in first 20 bytes following RESBUF. The result + must be in little endian byte order. */ +void* sha1_read_ctx(const struct sha1_ctx* ctx, void* resbuf) { + char* r = resbuf; + set_uint32(r + 0 * sizeof ctx->A, SWAP(ctx->A)); + set_uint32(r + 1 * sizeof ctx->B, SWAP(ctx->B)); + set_uint32(r + 2 * sizeof ctx->C, SWAP(ctx->C)); + set_uint32(r + 3 * sizeof ctx->D, SWAP(ctx->D)); + set_uint32(r + 4 * sizeof ctx->E, SWAP(ctx->E)); + + return resbuf; +} + +/* Process the remaining bytes in the internal buffer and the usual + prolog according to the standard and write the result to RESBUF. */ +void* sha1_finish_ctx(struct sha1_ctx* ctx, void* resbuf) { + /* Take yet unprocessed bytes into account. */ + uint32_t bytes = ctx->buflen; + size_t size = (bytes < 56) ? 64 / 4 : 64 * 2 / 4; + + /* Now count remaining bytes. */ + ctx->total[0] += bytes; + if(ctx->total[0] < bytes) ++ctx->total[1]; + + /* Put the 64-bit file length in *bits* at the end of the buffer. */ + ctx->buffer[size - 2] = SWAP((ctx->total[1] << 3) | (ctx->total[0] >> 29)); + ctx->buffer[size - 1] = SWAP(ctx->total[0] << 3); + + memcpy(&((char*)ctx->buffer)[bytes], fillbuf, (size - 2) * 4 - bytes); + + /* Process last bytes. */ + sha1_process_block(ctx->buffer, size * 4, ctx); + + return sha1_read_ctx(ctx, resbuf); +} + +/* Compute SHA1 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +void* sha1_buffer(const char* buffer, size_t len, void* resblock) { + struct sha1_ctx ctx; + + /* Initialize the computation context. */ + sha1_init_ctx(&ctx); + + /* Process whole buffer but last len % 64 bytes. */ + sha1_process_bytes(buffer, len, &ctx); + + /* Put result in desired memory area. */ + return sha1_finish_ctx(&ctx, resblock); +} + +void sha1_process_bytes(const void* buffer, size_t len, struct sha1_ctx* ctx) { + /* When we already have some bits in our internal buffer concatenate + both inputs first. */ + if(ctx->buflen != 0) { + size_t left_over = ctx->buflen; + size_t add = 128 - left_over > len ? len : 128 - left_over; + + memcpy(&((char*)ctx->buffer)[left_over], buffer, add); + ctx->buflen += add; + + if(ctx->buflen > 64) { + sha1_process_block(ctx->buffer, ctx->buflen & ~63, ctx); + + ctx->buflen &= 63; + /* The regions in the following copy operation cannot overlap, + because ctx->buflen < 64 ≤ (left_over + add) & ~63. */ + memcpy(ctx->buffer, &((char*)ctx->buffer)[(left_over + add) & ~63], ctx->buflen); + } + + buffer = (const char*)buffer + add; + len -= add; + } + + /* Process available complete blocks. */ + if(len >= 64) { +#if !(_STRING_ARCH_unaligned || _STRING_INLINE_unaligned) +#define UNALIGNED_P(p) ((uintptr_t)(p) % sizeof(uint32_t) != 0) + if(UNALIGNED_P(buffer)) + while(len > 64) { + sha1_process_block(memcpy(ctx->buffer, buffer, 64), 64, ctx); + buffer = (const char*)buffer + 64; + len -= 64; + } + else +#endif + { + sha1_process_block(buffer, len & ~63, ctx); + buffer = (const char*)buffer + (len & ~63); + len &= 63; + } + } + + /* Move remaining bytes in internal buffer. */ + if(len > 0) { + size_t left_over = ctx->buflen; + + memcpy(&((char*)ctx->buffer)[left_over], buffer, len); + left_over += len; + if(left_over >= 64) { + sha1_process_block(ctx->buffer, 64, ctx); + left_over -= 64; + /* The regions in the following copy operation cannot overlap, + because left_over ≤ 64. */ + memcpy(ctx->buffer, &ctx->buffer[16], left_over); + } + ctx->buflen = left_over; + } +} + +/* --- Code below is the primary difference between md5.c and sha1.c --- */ + +/* SHA1 round constants */ +#define K1 0x5a827999 +#define K2 0x6ed9eba1 +#define K3 0x8f1bbcdc +#define K4 0xca62c1d6 + +/* Round functions. Note that F2 is the same as F4. */ +#define F1(B, C, D) (D ^ (B & (C ^ D))) +#define F2(B, C, D) (B ^ C ^ D) +#define F3(B, C, D) ((B & C) | (D & (B | C))) +#define F4(B, C, D) (B ^ C ^ D) + +/* Process LEN bytes of BUFFER, accumulating context into CTX. + It is assumed that LEN % 64 == 0. + Most of this code comes from GnuPG's cipher/sha1.c. */ + +void sha1_process_block(const void* buffer, size_t len, struct sha1_ctx* ctx) { + const uint32_t* words = buffer; + size_t nwords = len / sizeof(uint32_t); + const uint32_t* endp = words + nwords; + uint32_t x[16]; + uint32_t a = ctx->A; + uint32_t b = ctx->B; + uint32_t c = ctx->C; + uint32_t d = ctx->D; + uint32_t e = ctx->E; + uint32_t lolen = len; + + /* First increment the byte count. RFC 1321 specifies the possible + length of the file up to 2^64 bits. Here we only compute the + number of bytes. Do a double word increment. */ + ctx->total[0] += lolen; + ctx->total[1] += (len >> 31 >> 1) + (ctx->total[0] < lolen); + +#define rol(x, n) (((x) << (n)) | ((uint32_t)(x) >> (32 - (n)))) + +#define M(I) \ + (tm = x[I & 0x0f] ^ x[(I - 14) & 0x0f] ^ x[(I - 8) & 0x0f] ^ x[(I - 3) & 0x0f], \ + (x[I & 0x0f] = rol(tm, 1))) + +#define R(A, B, C, D, E, F, K, M) \ + do { \ + E += rol(A, 5) + F(B, C, D) + K + M; \ + B = rol(B, 30); \ + } while(0) + + while(words < endp) { + uint32_t tm; + int t; + for(t = 0; t < 16; t++) { + x[t] = SWAP(*words); + words++; + } + + R(a, b, c, d, e, F1, K1, x[0]); + R(e, a, b, c, d, F1, K1, x[1]); + R(d, e, a, b, c, F1, K1, x[2]); + R(c, d, e, a, b, F1, K1, x[3]); + R(b, c, d, e, a, F1, K1, x[4]); + R(a, b, c, d, e, F1, K1, x[5]); + R(e, a, b, c, d, F1, K1, x[6]); + R(d, e, a, b, c, F1, K1, x[7]); + R(c, d, e, a, b, F1, K1, x[8]); + R(b, c, d, e, a, F1, K1, x[9]); + R(a, b, c, d, e, F1, K1, x[10]); + R(e, a, b, c, d, F1, K1, x[11]); + R(d, e, a, b, c, F1, K1, x[12]); + R(c, d, e, a, b, F1, K1, x[13]); + R(b, c, d, e, a, F1, K1, x[14]); + R(a, b, c, d, e, F1, K1, x[15]); + R(e, a, b, c, d, F1, K1, M(16)); + R(d, e, a, b, c, F1, K1, M(17)); + R(c, d, e, a, b, F1, K1, M(18)); + R(b, c, d, e, a, F1, K1, M(19)); + R(a, b, c, d, e, F2, K2, M(20)); + R(e, a, b, c, d, F2, K2, M(21)); + R(d, e, a, b, c, F2, K2, M(22)); + R(c, d, e, a, b, F2, K2, M(23)); + R(b, c, d, e, a, F2, K2, M(24)); + R(a, b, c, d, e, F2, K2, M(25)); + R(e, a, b, c, d, F2, K2, M(26)); + R(d, e, a, b, c, F2, K2, M(27)); + R(c, d, e, a, b, F2, K2, M(28)); + R(b, c, d, e, a, F2, K2, M(29)); + R(a, b, c, d, e, F2, K2, M(30)); + R(e, a, b, c, d, F2, K2, M(31)); + R(d, e, a, b, c, F2, K2, M(32)); + R(c, d, e, a, b, F2, K2, M(33)); + R(b, c, d, e, a, F2, K2, M(34)); + R(a, b, c, d, e, F2, K2, M(35)); + R(e, a, b, c, d, F2, K2, M(36)); + R(d, e, a, b, c, F2, K2, M(37)); + R(c, d, e, a, b, F2, K2, M(38)); + R(b, c, d, e, a, F2, K2, M(39)); + R(a, b, c, d, e, F3, K3, M(40)); + R(e, a, b, c, d, F3, K3, M(41)); + R(d, e, a, b, c, F3, K3, M(42)); + R(c, d, e, a, b, F3, K3, M(43)); + R(b, c, d, e, a, F3, K3, M(44)); + R(a, b, c, d, e, F3, K3, M(45)); + R(e, a, b, c, d, F3, K3, M(46)); + R(d, e, a, b, c, F3, K3, M(47)); + R(c, d, e, a, b, F3, K3, M(48)); + R(b, c, d, e, a, F3, K3, M(49)); + R(a, b, c, d, e, F3, K3, M(50)); + R(e, a, b, c, d, F3, K3, M(51)); + R(d, e, a, b, c, F3, K3, M(52)); + R(c, d, e, a, b, F3, K3, M(53)); + R(b, c, d, e, a, F3, K3, M(54)); + R(a, b, c, d, e, F3, K3, M(55)); + R(e, a, b, c, d, F3, K3, M(56)); + R(d, e, a, b, c, F3, K3, M(57)); + R(c, d, e, a, b, F3, K3, M(58)); + R(b, c, d, e, a, F3, K3, M(59)); + R(a, b, c, d, e, F4, K4, M(60)); + R(e, a, b, c, d, F4, K4, M(61)); + R(d, e, a, b, c, F4, K4, M(62)); + R(c, d, e, a, b, F4, K4, M(63)); + R(b, c, d, e, a, F4, K4, M(64)); + R(a, b, c, d, e, F4, K4, M(65)); + R(e, a, b, c, d, F4, K4, M(66)); + R(d, e, a, b, c, F4, K4, M(67)); + R(c, d, e, a, b, F4, K4, M(68)); + R(b, c, d, e, a, F4, K4, M(69)); + R(a, b, c, d, e, F4, K4, M(70)); + R(e, a, b, c, d, F4, K4, M(71)); + R(d, e, a, b, c, F4, K4, M(72)); + R(c, d, e, a, b, F4, K4, M(73)); + R(b, c, d, e, a, F4, K4, M(74)); + R(a, b, c, d, e, F4, K4, M(75)); + R(e, a, b, c, d, F4, K4, M(76)); + R(d, e, a, b, c, F4, K4, M(77)); + R(c, d, e, a, b, F4, K4, M(78)); + R(b, c, d, e, a, F4, K4, M(79)); + + a = ctx->A += a; + b = ctx->B += b; + c = ctx->C += c; + d = ctx->D += d; + e = ctx->E += e; + } +} + +#endif + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/applications/plugins/totp/services/hmac/sha1.h b/applications/plugins/totp/services/hmac/sha1.h new file mode 100644 index 000000000..d602d26fd --- /dev/null +++ b/applications/plugins/totp/services/hmac/sha1.h @@ -0,0 +1,105 @@ +/* Declarations of functions and data types used for SHA1 sum + library functions. + Copyright (C) 2000-2001, 2003, 2005-2006, 2008-2022 Free Software + Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#pragma once + +#include +#include + +#if HAVE_OPENSSL_SHA1 +#ifndef OPENSSL_API_COMPAT +#define OPENSSL_API_COMPAT 0x10101000L /* FIXME: Use OpenSSL 1.1+ API. */ +#endif +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define SHA1_DIGEST_SIZE 20 + +#if HAVE_OPENSSL_SHA1 +#define GL_OPENSSL_NAME 1 +#include "gl_openssl.h" +#else +/* Structure to save state of computation between the single steps. */ +struct sha1_ctx { + uint32_t A; + uint32_t B; + uint32_t C; + uint32_t D; + uint32_t E; + + uint32_t total[2]; + uint32_t buflen; /* ≥ 0, ≤ 128 */ + uint32_t buffer[32]; /* 128 bytes; the first buflen bytes are in use */ +}; + +/* Initialize structure containing state of computation. */ +extern void sha1_init_ctx(struct sha1_ctx* ctx); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is necessary that LEN is a multiple of 64!!! */ +extern void sha1_process_block(const void* buffer, size_t len, struct sha1_ctx* ctx); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is NOT required that LEN is a multiple of 64. */ +extern void sha1_process_bytes(const void* buffer, size_t len, struct sha1_ctx* ctx); + +/* Process the remaining bytes in the buffer and put result from CTX + in first 20 bytes following RESBUF. The result is always in little + endian byte order, so that a byte-wise output yields to the wanted + ASCII representation of the message digest. */ +extern void* sha1_finish_ctx(struct sha1_ctx* ctx, void* restrict resbuf); + +/* Put result from CTX in first 20 bytes following RESBUF. The result is + always in little endian byte order, so that a byte-wise output yields + to the wanted ASCII representation of the message digest. */ +extern void* sha1_read_ctx(const struct sha1_ctx* ctx, void* restrict resbuf); + +/* Compute SHA1 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +extern void* sha1_buffer(const char* buffer, size_t len, void* restrict resblock); + +#endif + +/* Compute SHA1 message digest for bytes read from STREAM. + STREAM is an open file stream. Regular files are handled more efficiently. + The contents of STREAM from its current position to its end will be read. + The case that the last operation on STREAM was an 'ungetc' is not supported. + The resulting message digest number will be written into the 20 bytes + beginning at RESBLOCK. */ +extern int sha1_stream(FILE* stream, void* resblock); + +#ifdef __cplusplus +} +#endif + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/applications/plugins/totp/services/hmac/sha256.c b/applications/plugins/totp/services/hmac/sha256.c new file mode 100644 index 000000000..2ea63dd37 --- /dev/null +++ b/applications/plugins/totp/services/hmac/sha256.c @@ -0,0 +1,385 @@ +/* sha256.c - Functions to compute SHA256 and SHA224 message digest of files or + memory blocks according to the NIST specification FIPS-180-2. + + Copyright (C) 2005-2006, 2008-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +/* Written by David Madore, considerably copypasting from + Scott G. Miller's sha1.c +*/ + +/* Specification. */ +#if HAVE_OPENSSL_SHA256 +#define GL_OPENSSL_INLINE _GL_EXTERN_INLINE +#endif +#include "sha256.h" + +#include +#include + +#ifdef WORDS_BIGENDIAN +#define SWAP(n) (n) +#else +#include "byteswap.h" +#define SWAP(n) swap_uint32(n) +#endif + +#if !HAVE_OPENSSL_SHA256 + +/* This array contains the bytes used to pad the buffer to the next + 64-byte boundary. */ +static const unsigned char fillbuf[64] = {0x80, 0 /* , 0, 0, ... */}; + +/* + Takes a pointer to a 256 bit block of data (eight 32 bit ints) and + initializes it to the start constants of the SHA256 algorithm. This + must be called before using hash in the call to sha256_hash +*/ +void sha256_init_ctx(struct sha256_ctx* ctx) { + ctx->state[0] = 0x6a09e667UL; + ctx->state[1] = 0xbb67ae85UL; + ctx->state[2] = 0x3c6ef372UL; + ctx->state[3] = 0xa54ff53aUL; + ctx->state[4] = 0x510e527fUL; + ctx->state[5] = 0x9b05688cUL; + ctx->state[6] = 0x1f83d9abUL; + ctx->state[7] = 0x5be0cd19UL; + + ctx->total[0] = ctx->total[1] = 0; + ctx->buflen = 0; +} + +void sha224_init_ctx(struct sha256_ctx* ctx) { + ctx->state[0] = 0xc1059ed8UL; + ctx->state[1] = 0x367cd507UL; + ctx->state[2] = 0x3070dd17UL; + ctx->state[3] = 0xf70e5939UL; + ctx->state[4] = 0xffc00b31UL; + ctx->state[5] = 0x68581511UL; + ctx->state[6] = 0x64f98fa7UL; + ctx->state[7] = 0xbefa4fa4UL; + + ctx->total[0] = ctx->total[1] = 0; + ctx->buflen = 0; +} + +/* Copy the value from v into the memory location pointed to by *CP, + If your architecture allows unaligned access, this is equivalent to + * (__typeof__ (v) *) cp = v */ +static void set_uint32(char* cp, uint32_t v) { + memcpy(cp, &v, sizeof v); +} + +/* Put result from CTX in first 32 bytes following RESBUF. + The result must be in little endian byte order. */ +void* sha256_read_ctx(const struct sha256_ctx* ctx, void* resbuf) { + int i; + char* r = resbuf; + + for(i = 0; i < 8; i++) set_uint32(r + i * sizeof ctx->state[0], SWAP(ctx->state[i])); + + return resbuf; +} + +void* sha224_read_ctx(const struct sha256_ctx* ctx, void* resbuf) { + int i; + char* r = resbuf; + + for(i = 0; i < 7; i++) set_uint32(r + i * sizeof ctx->state[0], SWAP(ctx->state[i])); + + return resbuf; +} + +/* Process the remaining bytes in the internal buffer and the usual + prolog according to the standard and write the result to RESBUF. */ +static void sha256_conclude_ctx(struct sha256_ctx* ctx) { + /* Take yet unprocessed bytes into account. */ + size_t bytes = ctx->buflen; + size_t size = (bytes < 56) ? 64 / 4 : 64 * 2 / 4; + + /* Now count remaining bytes. */ + ctx->total[0] += bytes; + if(ctx->total[0] < bytes) ++ctx->total[1]; + + /* Put the 64-bit file length in *bits* at the end of the buffer. + Use set_uint32 rather than a simple assignment, to avoid risk of + unaligned access. */ + set_uint32((char*)&ctx->buffer[size - 2], SWAP((ctx->total[1] << 3) | (ctx->total[0] >> 29))); + set_uint32((char*)&ctx->buffer[size - 1], SWAP(ctx->total[0] << 3)); + + memcpy(&((char*)ctx->buffer)[bytes], fillbuf, (size - 2) * 4 - bytes); + + /* Process last bytes. */ + sha256_process_block(ctx->buffer, size * 4, ctx); +} + +void* sha256_finish_ctx(struct sha256_ctx* ctx, void* resbuf) { + sha256_conclude_ctx(ctx); + return sha256_read_ctx(ctx, resbuf); +} + +void* sha224_finish_ctx(struct sha256_ctx* ctx, void* resbuf) { + sha256_conclude_ctx(ctx); + return sha224_read_ctx(ctx, resbuf); +} + +/* Compute SHA256 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +void* sha256_buffer(const char* buffer, size_t len, void* resblock) { + struct sha256_ctx ctx; + + /* Initialize the computation context. */ + sha256_init_ctx(&ctx); + + /* Process whole buffer but last len % 64 bytes. */ + sha256_process_bytes(buffer, len, &ctx); + + /* Put result in desired memory area. */ + return sha256_finish_ctx(&ctx, resblock); +} + +void* sha224_buffer(const char* buffer, size_t len, void* resblock) { + struct sha256_ctx ctx; + + /* Initialize the computation context. */ + sha224_init_ctx(&ctx); + + /* Process whole buffer but last len % 64 bytes. */ + sha256_process_bytes(buffer, len, &ctx); + + /* Put result in desired memory area. */ + return sha224_finish_ctx(&ctx, resblock); +} + +void sha256_process_bytes(const void* buffer, size_t len, struct sha256_ctx* ctx) { + /* When we already have some bits in our internal buffer concatenate + both inputs first. */ + if(ctx->buflen != 0) { + size_t left_over = ctx->buflen; + size_t add = 128 - left_over > len ? len : 128 - left_over; + + memcpy(&((char*)ctx->buffer)[left_over], buffer, add); + ctx->buflen += add; + + if(ctx->buflen > 64) { + sha256_process_block(ctx->buffer, ctx->buflen & ~63, ctx); + + ctx->buflen &= 63; + /* The regions in the following copy operation cannot overlap, + because ctx->buflen < 64 ≤ (left_over + add) & ~63. */ + memcpy(ctx->buffer, &((char*)ctx->buffer)[(left_over + add) & ~63], ctx->buflen); + } + + buffer = (const char*)buffer + add; + len -= add; + } + + /* Process available complete blocks. */ + if(len >= 64) { +#if !(_STRING_ARCH_unaligned || _STRING_INLINE_unaligned) +#define UNALIGNED_P(p) ((uintptr_t)(p) % sizeof(uint32_t) != 0) + if(UNALIGNED_P(buffer)) + while(len > 64) { + sha256_process_block(memcpy(ctx->buffer, buffer, 64), 64, ctx); + buffer = (const char*)buffer + 64; + len -= 64; + } + else +#endif + { + sha256_process_block(buffer, len & ~63, ctx); + buffer = (const char*)buffer + (len & ~63); + len &= 63; + } + } + + /* Move remaining bytes in internal buffer. */ + if(len > 0) { + size_t left_over = ctx->buflen; + + memcpy(&((char*)ctx->buffer)[left_over], buffer, len); + left_over += len; + if(left_over >= 64) { + sha256_process_block(ctx->buffer, 64, ctx); + left_over -= 64; + /* The regions in the following copy operation cannot overlap, + because left_over ≤ 64. */ + memcpy(ctx->buffer, &ctx->buffer[16], left_over); + } + ctx->buflen = left_over; + } +} + +/* --- Code below is the primary difference between sha1.c and sha256.c --- */ + +/* SHA256 round constants */ +#define K(I) sha256_round_constants[I] +static const uint32_t sha256_round_constants[64] = { + 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL, 0x59f111f1UL, + 0x923f82a4UL, 0xab1c5ed5UL, 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, + 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL, 0xe49b69c1UL, 0xefbe4786UL, + 0x0fc19dc6UL, 0x240ca1ccUL, 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, + 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL, + 0x06ca6351UL, 0x14292967UL, 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, + 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, 0xa2bfe8a1UL, 0xa81a664bUL, + 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, + 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, + 0x5b9cca4fUL, 0x682e6ff3UL, 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, + 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL, +}; + +/* Round functions. */ +#define F2(A, B, C) ((A & B) | (C & (A | B))) +#define F1(E, F, G) (G ^ (E & (F ^ G))) + +/* Process LEN bytes of BUFFER, accumulating context into CTX. + It is assumed that LEN % 64 == 0. + Most of this code comes from GnuPG's cipher/sha1.c. */ + +void sha256_process_block(const void* buffer, size_t len, struct sha256_ctx* ctx) { + const uint32_t* words = buffer; + size_t nwords = len / sizeof(uint32_t); + const uint32_t* endp = words + nwords; + uint32_t x[16]; + uint32_t a = ctx->state[0]; + uint32_t b = ctx->state[1]; + uint32_t c = ctx->state[2]; + uint32_t d = ctx->state[3]; + uint32_t e = ctx->state[4]; + uint32_t f = ctx->state[5]; + uint32_t g = ctx->state[6]; + uint32_t h = ctx->state[7]; + uint32_t lolen = len; + + /* First increment the byte count. FIPS PUB 180-2 specifies the possible + length of the file up to 2^64 bits. Here we only compute the + number of bytes. Do a double word increment. */ + ctx->total[0] += lolen; + ctx->total[1] += (len >> 31 >> 1) + (ctx->total[0] < lolen); + +#define rol(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) +#define S0(x) (rol(x, 25) ^ rol(x, 14) ^ (x >> 3)) +#define S1(x) (rol(x, 15) ^ rol(x, 13) ^ (x >> 10)) +#define SS0(x) (rol(x, 30) ^ rol(x, 19) ^ rol(x, 10)) +#define SS1(x) (rol(x, 26) ^ rol(x, 21) ^ rol(x, 7)) + +#define M(I) \ + (tm = S1(x[(I - 2) & 0x0f]) + x[(I - 7) & 0x0f] + S0(x[(I - 15) & 0x0f]) + x[I & 0x0f], \ + x[I & 0x0f] = tm) + +#define R(A, B, C, D, E, F, G, H, K, M) \ + do { \ + t0 = SS0(A) + F2(A, B, C); \ + t1 = H + SS1(E) + F1(E, F, G) + K + M; \ + D += t1; \ + H = t0 + t1; \ + } while(0) + + while(words < endp) { + uint32_t tm; + uint32_t t0, t1; + int t; + /* FIXME: see sha1.c for a better implementation. */ + for(t = 0; t < 16; t++) { + x[t] = SWAP(*words); + words++; + } + + R(a, b, c, d, e, f, g, h, K(0), x[0]); + R(h, a, b, c, d, e, f, g, K(1), x[1]); + R(g, h, a, b, c, d, e, f, K(2), x[2]); + R(f, g, h, a, b, c, d, e, K(3), x[3]); + R(e, f, g, h, a, b, c, d, K(4), x[4]); + R(d, e, f, g, h, a, b, c, K(5), x[5]); + R(c, d, e, f, g, h, a, b, K(6), x[6]); + R(b, c, d, e, f, g, h, a, K(7), x[7]); + R(a, b, c, d, e, f, g, h, K(8), x[8]); + R(h, a, b, c, d, e, f, g, K(9), x[9]); + R(g, h, a, b, c, d, e, f, K(10), x[10]); + R(f, g, h, a, b, c, d, e, K(11), x[11]); + R(e, f, g, h, a, b, c, d, K(12), x[12]); + R(d, e, f, g, h, a, b, c, K(13), x[13]); + R(c, d, e, f, g, h, a, b, K(14), x[14]); + R(b, c, d, e, f, g, h, a, K(15), x[15]); + R(a, b, c, d, e, f, g, h, K(16), M(16)); + R(h, a, b, c, d, e, f, g, K(17), M(17)); + R(g, h, a, b, c, d, e, f, K(18), M(18)); + R(f, g, h, a, b, c, d, e, K(19), M(19)); + R(e, f, g, h, a, b, c, d, K(20), M(20)); + R(d, e, f, g, h, a, b, c, K(21), M(21)); + R(c, d, e, f, g, h, a, b, K(22), M(22)); + R(b, c, d, e, f, g, h, a, K(23), M(23)); + R(a, b, c, d, e, f, g, h, K(24), M(24)); + R(h, a, b, c, d, e, f, g, K(25), M(25)); + R(g, h, a, b, c, d, e, f, K(26), M(26)); + R(f, g, h, a, b, c, d, e, K(27), M(27)); + R(e, f, g, h, a, b, c, d, K(28), M(28)); + R(d, e, f, g, h, a, b, c, K(29), M(29)); + R(c, d, e, f, g, h, a, b, K(30), M(30)); + R(b, c, d, e, f, g, h, a, K(31), M(31)); + R(a, b, c, d, e, f, g, h, K(32), M(32)); + R(h, a, b, c, d, e, f, g, K(33), M(33)); + R(g, h, a, b, c, d, e, f, K(34), M(34)); + R(f, g, h, a, b, c, d, e, K(35), M(35)); + R(e, f, g, h, a, b, c, d, K(36), M(36)); + R(d, e, f, g, h, a, b, c, K(37), M(37)); + R(c, d, e, f, g, h, a, b, K(38), M(38)); + R(b, c, d, e, f, g, h, a, K(39), M(39)); + R(a, b, c, d, e, f, g, h, K(40), M(40)); + R(h, a, b, c, d, e, f, g, K(41), M(41)); + R(g, h, a, b, c, d, e, f, K(42), M(42)); + R(f, g, h, a, b, c, d, e, K(43), M(43)); + R(e, f, g, h, a, b, c, d, K(44), M(44)); + R(d, e, f, g, h, a, b, c, K(45), M(45)); + R(c, d, e, f, g, h, a, b, K(46), M(46)); + R(b, c, d, e, f, g, h, a, K(47), M(47)); + R(a, b, c, d, e, f, g, h, K(48), M(48)); + R(h, a, b, c, d, e, f, g, K(49), M(49)); + R(g, h, a, b, c, d, e, f, K(50), M(50)); + R(f, g, h, a, b, c, d, e, K(51), M(51)); + R(e, f, g, h, a, b, c, d, K(52), M(52)); + R(d, e, f, g, h, a, b, c, K(53), M(53)); + R(c, d, e, f, g, h, a, b, K(54), M(54)); + R(b, c, d, e, f, g, h, a, K(55), M(55)); + R(a, b, c, d, e, f, g, h, K(56), M(56)); + R(h, a, b, c, d, e, f, g, K(57), M(57)); + R(g, h, a, b, c, d, e, f, K(58), M(58)); + R(f, g, h, a, b, c, d, e, K(59), M(59)); + R(e, f, g, h, a, b, c, d, K(60), M(60)); + R(d, e, f, g, h, a, b, c, K(61), M(61)); + R(c, d, e, f, g, h, a, b, K(62), M(62)); + R(b, c, d, e, f, g, h, a, K(63), M(63)); + + a = ctx->state[0] += a; + b = ctx->state[1] += b; + c = ctx->state[2] += c; + d = ctx->state[3] += d; + e = ctx->state[4] += e; + f = ctx->state[5] += f; + g = ctx->state[6] += g; + h = ctx->state[7] += h; + } +} + +#endif + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/applications/plugins/totp/services/hmac/sha256.h b/applications/plugins/totp/services/hmac/sha256.h new file mode 100644 index 000000000..2f99d1428 --- /dev/null +++ b/applications/plugins/totp/services/hmac/sha256.h @@ -0,0 +1,108 @@ +/* Declarations of functions and data types used for SHA256 and SHA224 sum + library functions. + Copyright (C) 2005-2006, 2008-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#pragma once + +#include +#include + +#if HAVE_OPENSSL_SHA256 +#ifndef OPENSSL_API_COMPAT +#define OPENSSL_API_COMPAT 0x10101000L /* FIXME: Use OpenSSL 1.1+ API. */ +#endif +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +enum { SHA224_DIGEST_SIZE = 224 / 8 }; +enum { SHA256_DIGEST_SIZE = 256 / 8 }; + +#if HAVE_OPENSSL_SHA256 +#define GL_OPENSSL_NAME 224 +#include "gl_openssl.h" +#define GL_OPENSSL_NAME 256 +#include "gl_openssl.h" +#else +/* Structure to save state of computation between the single steps. */ +struct sha256_ctx { + uint32_t state[8]; + + uint32_t total[2]; + size_t buflen; /* ≥ 0, ≤ 128 */ + uint32_t buffer[32]; /* 128 bytes; the first buflen bytes are in use */ +}; + +/* Initialize structure containing state of computation. */ +extern void sha256_init_ctx(struct sha256_ctx* ctx); +extern void sha224_init_ctx(struct sha256_ctx* ctx); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is necessary that LEN is a multiple of 64!!! */ +extern void sha256_process_block(const void* buffer, size_t len, struct sha256_ctx* ctx); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is NOT required that LEN is a multiple of 64. */ +extern void sha256_process_bytes(const void* buffer, size_t len, struct sha256_ctx* ctx); + +/* Process the remaining bytes in the buffer and put result from CTX + in first 32 (28) bytes following RESBUF. The result is always in little + endian byte order, so that a byte-wise output yields to the wanted + ASCII representation of the message digest. */ +extern void* sha256_finish_ctx(struct sha256_ctx* ctx, void* restrict resbuf); +extern void* sha224_finish_ctx(struct sha256_ctx* ctx, void* restrict resbuf); + +/* Put result from CTX in first 32 (28) bytes following RESBUF. The result is + always in little endian byte order, so that a byte-wise output yields + to the wanted ASCII representation of the message digest. */ +extern void* sha256_read_ctx(const struct sha256_ctx* ctx, void* restrict resbuf); +extern void* sha224_read_ctx(const struct sha256_ctx* ctx, void* restrict resbuf); + +/* Compute SHA256 (SHA224) message digest for LEN bytes beginning at BUFFER. + The result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +extern void* sha256_buffer(const char* buffer, size_t len, void* restrict resblock); +extern void* sha224_buffer(const char* buffer, size_t len, void* restrict resblock); + +#endif + +/* Compute SHA256 (SHA224) message digest for bytes read from STREAM. + STREAM is an open file stream. Regular files are handled more efficiently. + The contents of STREAM from its current position to its end will be read. + The case that the last operation on STREAM was an 'ungetc' is not supported. + The resulting message digest number will be written into the 32 (28) bytes + beginning at RESBLOCK. */ +extern int sha256_stream(FILE* stream, void* resblock); +extern int sha224_stream(FILE* stream, void* resblock); + +#ifdef __cplusplus +} +#endif + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/applications/plugins/totp/services/hmac/sha512.c b/applications/plugins/totp/services/hmac/sha512.c new file mode 100644 index 000000000..53d3bad2e --- /dev/null +++ b/applications/plugins/totp/services/hmac/sha512.c @@ -0,0 +1,430 @@ +/* sha512.c - Functions to compute SHA512 and SHA384 message digest of files or + memory blocks according to the NIST specification FIPS-180-2. + + Copyright (C) 2005-2006, 2008-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +/* Written by David Madore, considerably copypasting from + Scott G. Miller's sha1.c +*/ + +/* Specification. */ +#if HAVE_OPENSSL_SHA512 +#define GL_OPENSSL_INLINE _GL_EXTERN_INLINE +#endif +#include "sha512.h" + +#include +#include + +#ifdef WORDS_BIGENDIAN +#define SWAP(n) (n) +#else +#include "byteswap.h" +#define SWAP(n) swap_uint64(n) +#endif + +#if !HAVE_OPENSSL_SHA512 + +/* This array contains the bytes used to pad the buffer to the next + 128-byte boundary. */ +static const unsigned char fillbuf[128] = {0x80, 0 /* , 0, 0, ... */}; + +/* + Takes a pointer to a 512 bit block of data (eight 64 bit ints) and + initializes it to the start constants of the SHA512 algorithm. This + must be called before using hash in the call to sha512_hash +*/ +void sha512_init_ctx(struct sha512_ctx* ctx) { + ctx->state[0] = u64hilo(0x6a09e667, 0xf3bcc908); + ctx->state[1] = u64hilo(0xbb67ae85, 0x84caa73b); + ctx->state[2] = u64hilo(0x3c6ef372, 0xfe94f82b); + ctx->state[3] = u64hilo(0xa54ff53a, 0x5f1d36f1); + ctx->state[4] = u64hilo(0x510e527f, 0xade682d1); + ctx->state[5] = u64hilo(0x9b05688c, 0x2b3e6c1f); + ctx->state[6] = u64hilo(0x1f83d9ab, 0xfb41bd6b); + ctx->state[7] = u64hilo(0x5be0cd19, 0x137e2179); + + ctx->total[0] = ctx->total[1] = u64lo(0); + ctx->buflen = 0; +} + +void sha384_init_ctx(struct sha512_ctx* ctx) { + ctx->state[0] = u64hilo(0xcbbb9d5d, 0xc1059ed8); + ctx->state[1] = u64hilo(0x629a292a, 0x367cd507); + ctx->state[2] = u64hilo(0x9159015a, 0x3070dd17); + ctx->state[3] = u64hilo(0x152fecd8, 0xf70e5939); + ctx->state[4] = u64hilo(0x67332667, 0xffc00b31); + ctx->state[5] = u64hilo(0x8eb44a87, 0x68581511); + ctx->state[6] = u64hilo(0xdb0c2e0d, 0x64f98fa7); + ctx->state[7] = u64hilo(0x47b5481d, 0xbefa4fa4); + + ctx->total[0] = ctx->total[1] = u64lo(0); + ctx->buflen = 0; +} + +/* Copy the value from V into the memory location pointed to by *CP, + If your architecture allows unaligned access, this is equivalent to + * (__typeof__ (v) *) cp = v */ +static void set_uint64(char* cp, u64 v) { + memcpy(cp, &v, sizeof v); +} + +/* Put result from CTX in first 64 bytes following RESBUF. + The result must be in little endian byte order. */ +void* sha512_read_ctx(const struct sha512_ctx* ctx, void* resbuf) { + int i; + char* r = resbuf; + + for(i = 0; i < 8; i++) set_uint64(r + i * sizeof ctx->state[0], SWAP(ctx->state[i])); + + return resbuf; +} + +void* sha384_read_ctx(const struct sha512_ctx* ctx, void* resbuf) { + int i; + char* r = resbuf; + + for(i = 0; i < 6; i++) set_uint64(r + i * sizeof ctx->state[0], SWAP(ctx->state[i])); + + return resbuf; +} + +/* Process the remaining bytes in the internal buffer and the usual + prolog according to the standard and write the result to RESBUF. */ +static void sha512_conclude_ctx(struct sha512_ctx* ctx) { + /* Take yet unprocessed bytes into account. */ + size_t bytes = ctx->buflen; + size_t size = (bytes < 112) ? 128 / 8 : 128 * 2 / 8; + + /* Now count remaining bytes. */ + ctx->total[0] = u64plus(ctx->total[0], u64lo(bytes)); + if(u64lt(ctx->total[0], u64lo(bytes))) ctx->total[1] = u64plus(ctx->total[1], u64lo(1)); + + /* Put the 128-bit file length in *bits* at the end of the buffer. + Use set_uint64 rather than a simple assignment, to avoid risk of + unaligned access. */ + set_uint64( + (char*)&ctx->buffer[size - 2], + SWAP(u64or(u64shl(ctx->total[1], 3), u64shr(ctx->total[0], 61)))); + set_uint64((char*)&ctx->buffer[size - 1], SWAP(u64shl(ctx->total[0], 3))); + + memcpy(&((char*)ctx->buffer)[bytes], fillbuf, (size - 2) * 8 - bytes); + + /* Process last bytes. */ + sha512_process_block(ctx->buffer, size * 8, ctx); +} + +void* sha512_finish_ctx(struct sha512_ctx* ctx, void* resbuf) { + sha512_conclude_ctx(ctx); + return sha512_read_ctx(ctx, resbuf); +} + +void* sha384_finish_ctx(struct sha512_ctx* ctx, void* resbuf) { + sha512_conclude_ctx(ctx); + return sha384_read_ctx(ctx, resbuf); +} + +/* Compute SHA512 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +void* sha512_buffer(const char* buffer, size_t len, void* resblock) { + struct sha512_ctx ctx; + + /* Initialize the computation context. */ + sha512_init_ctx(&ctx); + + /* Process whole buffer but last len % 128 bytes. */ + sha512_process_bytes(buffer, len, &ctx); + + /* Put result in desired memory area. */ + return sha512_finish_ctx(&ctx, resblock); +} + +void* sha384_buffer(const char* buffer, size_t len, void* resblock) { + struct sha512_ctx ctx; + + /* Initialize the computation context. */ + sha384_init_ctx(&ctx); + + /* Process whole buffer but last len % 128 bytes. */ + sha512_process_bytes(buffer, len, &ctx); + + /* Put result in desired memory area. */ + return sha384_finish_ctx(&ctx, resblock); +} + +void sha512_process_bytes(const void* buffer, size_t len, struct sha512_ctx* ctx) { + /* When we already have some bits in our internal buffer concatenate + both inputs first. */ + if(ctx->buflen != 0) { + size_t left_over = ctx->buflen; + size_t add = 256 - left_over > len ? len : 256 - left_over; + + memcpy(&((char*)ctx->buffer)[left_over], buffer, add); + ctx->buflen += add; + + if(ctx->buflen > 128) { + sha512_process_block(ctx->buffer, ctx->buflen & ~127, ctx); + + ctx->buflen &= 127; + /* The regions in the following copy operation cannot overlap, + because ctx->buflen < 128 ≤ (left_over + add) & ~127. */ + memcpy(ctx->buffer, &((char*)ctx->buffer)[(left_over + add) & ~127], ctx->buflen); + } + + buffer = (const char*)buffer + add; + len -= add; + } + + /* Process available complete blocks. */ + if(len >= 128) { +#if !(_STRING_ARCH_unaligned || _STRING_INLINE_unaligned) +#define UNALIGNED_P(p) ((uintptr_t)(p) % sizeof(u64) != 0) + if(UNALIGNED_P(buffer)) + while(len > 128) { + sha512_process_block(memcpy(ctx->buffer, buffer, 128), 128, ctx); + buffer = (const char*)buffer + 128; + len -= 128; + } + else +#endif + { + sha512_process_block(buffer, len & ~127, ctx); + buffer = (const char*)buffer + (len & ~127); + len &= 127; + } + } + + /* Move remaining bytes in internal buffer. */ + if(len > 0) { + size_t left_over = ctx->buflen; + + memcpy(&((char*)ctx->buffer)[left_over], buffer, len); + left_over += len; + if(left_over >= 128) { + sha512_process_block(ctx->buffer, 128, ctx); + left_over -= 128; + /* The regions in the following copy operation cannot overlap, + because left_over ≤ 128. */ + memcpy(ctx->buffer, &ctx->buffer[16], left_over); + } + ctx->buflen = left_over; + } +} + +/* --- Code below is the primary difference between sha1.c and sha512.c --- */ + +/* SHA512 round constants */ +#define K(I) sha512_round_constants[I] +static u64 const sha512_round_constants[80] = { + u64init(0x428a2f98, 0xd728ae22), u64init(0x71374491, 0x23ef65cd), + u64init(0xb5c0fbcf, 0xec4d3b2f), u64init(0xe9b5dba5, 0x8189dbbc), + u64init(0x3956c25b, 0xf348b538), u64init(0x59f111f1, 0xb605d019), + u64init(0x923f82a4, 0xaf194f9b), u64init(0xab1c5ed5, 0xda6d8118), + u64init(0xd807aa98, 0xa3030242), u64init(0x12835b01, 0x45706fbe), + u64init(0x243185be, 0x4ee4b28c), u64init(0x550c7dc3, 0xd5ffb4e2), + u64init(0x72be5d74, 0xf27b896f), u64init(0x80deb1fe, 0x3b1696b1), + u64init(0x9bdc06a7, 0x25c71235), u64init(0xc19bf174, 0xcf692694), + u64init(0xe49b69c1, 0x9ef14ad2), u64init(0xefbe4786, 0x384f25e3), + u64init(0x0fc19dc6, 0x8b8cd5b5), u64init(0x240ca1cc, 0x77ac9c65), + u64init(0x2de92c6f, 0x592b0275), u64init(0x4a7484aa, 0x6ea6e483), + u64init(0x5cb0a9dc, 0xbd41fbd4), u64init(0x76f988da, 0x831153b5), + u64init(0x983e5152, 0xee66dfab), u64init(0xa831c66d, 0x2db43210), + u64init(0xb00327c8, 0x98fb213f), u64init(0xbf597fc7, 0xbeef0ee4), + u64init(0xc6e00bf3, 0x3da88fc2), u64init(0xd5a79147, 0x930aa725), + u64init(0x06ca6351, 0xe003826f), u64init(0x14292967, 0x0a0e6e70), + u64init(0x27b70a85, 0x46d22ffc), u64init(0x2e1b2138, 0x5c26c926), + u64init(0x4d2c6dfc, 0x5ac42aed), u64init(0x53380d13, 0x9d95b3df), + u64init(0x650a7354, 0x8baf63de), u64init(0x766a0abb, 0x3c77b2a8), + u64init(0x81c2c92e, 0x47edaee6), u64init(0x92722c85, 0x1482353b), + u64init(0xa2bfe8a1, 0x4cf10364), u64init(0xa81a664b, 0xbc423001), + u64init(0xc24b8b70, 0xd0f89791), u64init(0xc76c51a3, 0x0654be30), + u64init(0xd192e819, 0xd6ef5218), u64init(0xd6990624, 0x5565a910), + u64init(0xf40e3585, 0x5771202a), u64init(0x106aa070, 0x32bbd1b8), + u64init(0x19a4c116, 0xb8d2d0c8), u64init(0x1e376c08, 0x5141ab53), + u64init(0x2748774c, 0xdf8eeb99), u64init(0x34b0bcb5, 0xe19b48a8), + u64init(0x391c0cb3, 0xc5c95a63), u64init(0x4ed8aa4a, 0xe3418acb), + u64init(0x5b9cca4f, 0x7763e373), u64init(0x682e6ff3, 0xd6b2b8a3), + u64init(0x748f82ee, 0x5defb2fc), u64init(0x78a5636f, 0x43172f60), + u64init(0x84c87814, 0xa1f0ab72), u64init(0x8cc70208, 0x1a6439ec), + u64init(0x90befffa, 0x23631e28), u64init(0xa4506ceb, 0xde82bde9), + u64init(0xbef9a3f7, 0xb2c67915), u64init(0xc67178f2, 0xe372532b), + u64init(0xca273ece, 0xea26619c), u64init(0xd186b8c7, 0x21c0c207), + u64init(0xeada7dd6, 0xcde0eb1e), u64init(0xf57d4f7f, 0xee6ed178), + u64init(0x06f067aa, 0x72176fba), u64init(0x0a637dc5, 0xa2c898a6), + u64init(0x113f9804, 0xbef90dae), u64init(0x1b710b35, 0x131c471b), + u64init(0x28db77f5, 0x23047d84), u64init(0x32caab7b, 0x40c72493), + u64init(0x3c9ebe0a, 0x15c9bebc), u64init(0x431d67c4, 0x9c100d4c), + u64init(0x4cc5d4be, 0xcb3e42b6), u64init(0x597f299c, 0xfc657e2a), + u64init(0x5fcb6fab, 0x3ad6faec), u64init(0x6c44198c, 0x4a475817), +}; + +/* Round functions. */ +#define F2(A, B, C) u64or(u64and(A, B), u64and(C, u64or(A, B))) +#define F1(E, F, G) u64xor(G, u64and(E, u64xor(F, G))) + +/* Process LEN bytes of BUFFER, accumulating context into CTX. + It is assumed that LEN % 128 == 0. + Most of this code comes from GnuPG's cipher/sha1.c. */ + +void sha512_process_block(const void* buffer, size_t len, struct sha512_ctx* ctx) { + u64 const* words = buffer; + u64 const* endp = words + len / sizeof(u64); + u64 x[16]; + u64 a = ctx->state[0]; + u64 b = ctx->state[1]; + u64 c = ctx->state[2]; + u64 d = ctx->state[3]; + u64 e = ctx->state[4]; + u64 f = ctx->state[5]; + u64 g = ctx->state[6]; + u64 h = ctx->state[7]; + u64 lolen = u64size(len); + + /* First increment the byte count. FIPS PUB 180-2 specifies the possible + length of the file up to 2^128 bits. Here we only compute the + number of bytes. Do a double word increment. */ + ctx->total[0] = u64plus(ctx->total[0], lolen); + ctx->total[1] = u64plus( + ctx->total[1], u64plus(u64size(len >> 31 >> 31 >> 2), u64lo(u64lt(ctx->total[0], lolen)))); + +#define S0(x) u64xor(u64rol(x, 63), u64xor(u64rol(x, 56), u64shr(x, 7))) +#define S1(x) u64xor(u64rol(x, 45), u64xor(u64rol(x, 3), u64shr(x, 6))) +#define SS0(x) u64xor(u64rol(x, 36), u64xor(u64rol(x, 30), u64rol(x, 25))) +#define SS1(x) u64xor(u64rol(x, 50), u64xor(u64rol(x, 46), u64rol(x, 23))) + +#define M(I) \ + (x[(I)&15] = u64plus( \ + x[(I)&15], \ + u64plus(S1(x[((I)-2) & 15]), u64plus(x[((I)-7) & 15], S0(x[((I)-15) & 15]))))) + +#define R(A, B, C, D, E, F, G, H, K, M) \ + do { \ + u64 t0 = u64plus(SS0(A), F2(A, B, C)); \ + u64 t1 = u64plus(H, u64plus(SS1(E), u64plus(F1(E, F, G), u64plus(K, M)))); \ + D = u64plus(D, t1); \ + H = u64plus(t0, t1); \ + } while(0) + + while(words < endp) { + int t; + /* FIXME: see sha1.c for a better implementation. */ + for(t = 0; t < 16; t++) { + x[t] = SWAP(*words); + words++; + } + + R(a, b, c, d, e, f, g, h, K(0), x[0]); + R(h, a, b, c, d, e, f, g, K(1), x[1]); + R(g, h, a, b, c, d, e, f, K(2), x[2]); + R(f, g, h, a, b, c, d, e, K(3), x[3]); + R(e, f, g, h, a, b, c, d, K(4), x[4]); + R(d, e, f, g, h, a, b, c, K(5), x[5]); + R(c, d, e, f, g, h, a, b, K(6), x[6]); + R(b, c, d, e, f, g, h, a, K(7), x[7]); + R(a, b, c, d, e, f, g, h, K(8), x[8]); + R(h, a, b, c, d, e, f, g, K(9), x[9]); + R(g, h, a, b, c, d, e, f, K(10), x[10]); + R(f, g, h, a, b, c, d, e, K(11), x[11]); + R(e, f, g, h, a, b, c, d, K(12), x[12]); + R(d, e, f, g, h, a, b, c, K(13), x[13]); + R(c, d, e, f, g, h, a, b, K(14), x[14]); + R(b, c, d, e, f, g, h, a, K(15), x[15]); + R(a, b, c, d, e, f, g, h, K(16), M(16)); + R(h, a, b, c, d, e, f, g, K(17), M(17)); + R(g, h, a, b, c, d, e, f, K(18), M(18)); + R(f, g, h, a, b, c, d, e, K(19), M(19)); + R(e, f, g, h, a, b, c, d, K(20), M(20)); + R(d, e, f, g, h, a, b, c, K(21), M(21)); + R(c, d, e, f, g, h, a, b, K(22), M(22)); + R(b, c, d, e, f, g, h, a, K(23), M(23)); + R(a, b, c, d, e, f, g, h, K(24), M(24)); + R(h, a, b, c, d, e, f, g, K(25), M(25)); + R(g, h, a, b, c, d, e, f, K(26), M(26)); + R(f, g, h, a, b, c, d, e, K(27), M(27)); + R(e, f, g, h, a, b, c, d, K(28), M(28)); + R(d, e, f, g, h, a, b, c, K(29), M(29)); + R(c, d, e, f, g, h, a, b, K(30), M(30)); + R(b, c, d, e, f, g, h, a, K(31), M(31)); + R(a, b, c, d, e, f, g, h, K(32), M(32)); + R(h, a, b, c, d, e, f, g, K(33), M(33)); + R(g, h, a, b, c, d, e, f, K(34), M(34)); + R(f, g, h, a, b, c, d, e, K(35), M(35)); + R(e, f, g, h, a, b, c, d, K(36), M(36)); + R(d, e, f, g, h, a, b, c, K(37), M(37)); + R(c, d, e, f, g, h, a, b, K(38), M(38)); + R(b, c, d, e, f, g, h, a, K(39), M(39)); + R(a, b, c, d, e, f, g, h, K(40), M(40)); + R(h, a, b, c, d, e, f, g, K(41), M(41)); + R(g, h, a, b, c, d, e, f, K(42), M(42)); + R(f, g, h, a, b, c, d, e, K(43), M(43)); + R(e, f, g, h, a, b, c, d, K(44), M(44)); + R(d, e, f, g, h, a, b, c, K(45), M(45)); + R(c, d, e, f, g, h, a, b, K(46), M(46)); + R(b, c, d, e, f, g, h, a, K(47), M(47)); + R(a, b, c, d, e, f, g, h, K(48), M(48)); + R(h, a, b, c, d, e, f, g, K(49), M(49)); + R(g, h, a, b, c, d, e, f, K(50), M(50)); + R(f, g, h, a, b, c, d, e, K(51), M(51)); + R(e, f, g, h, a, b, c, d, K(52), M(52)); + R(d, e, f, g, h, a, b, c, K(53), M(53)); + R(c, d, e, f, g, h, a, b, K(54), M(54)); + R(b, c, d, e, f, g, h, a, K(55), M(55)); + R(a, b, c, d, e, f, g, h, K(56), M(56)); + R(h, a, b, c, d, e, f, g, K(57), M(57)); + R(g, h, a, b, c, d, e, f, K(58), M(58)); + R(f, g, h, a, b, c, d, e, K(59), M(59)); + R(e, f, g, h, a, b, c, d, K(60), M(60)); + R(d, e, f, g, h, a, b, c, K(61), M(61)); + R(c, d, e, f, g, h, a, b, K(62), M(62)); + R(b, c, d, e, f, g, h, a, K(63), M(63)); + R(a, b, c, d, e, f, g, h, K(64), M(64)); + R(h, a, b, c, d, e, f, g, K(65), M(65)); + R(g, h, a, b, c, d, e, f, K(66), M(66)); + R(f, g, h, a, b, c, d, e, K(67), M(67)); + R(e, f, g, h, a, b, c, d, K(68), M(68)); + R(d, e, f, g, h, a, b, c, K(69), M(69)); + R(c, d, e, f, g, h, a, b, K(70), M(70)); + R(b, c, d, e, f, g, h, a, K(71), M(71)); + R(a, b, c, d, e, f, g, h, K(72), M(72)); + R(h, a, b, c, d, e, f, g, K(73), M(73)); + R(g, h, a, b, c, d, e, f, K(74), M(74)); + R(f, g, h, a, b, c, d, e, K(75), M(75)); + R(e, f, g, h, a, b, c, d, K(76), M(76)); + R(d, e, f, g, h, a, b, c, K(77), M(77)); + R(c, d, e, f, g, h, a, b, K(78), M(78)); + R(b, c, d, e, f, g, h, a, K(79), M(79)); + + a = ctx->state[0] = u64plus(ctx->state[0], a); + b = ctx->state[1] = u64plus(ctx->state[1], b); + c = ctx->state[2] = u64plus(ctx->state[2], c); + d = ctx->state[3] = u64plus(ctx->state[3], d); + e = ctx->state[4] = u64plus(ctx->state[4], e); + f = ctx->state[5] = u64plus(ctx->state[5], f); + g = ctx->state[6] = u64plus(ctx->state[6], g); + h = ctx->state[7] = u64plus(ctx->state[7], h); + } +} + +#endif + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/applications/plugins/totp/services/hmac/sha512.h b/applications/plugins/totp/services/hmac/sha512.h new file mode 100644 index 000000000..6489fc3cb --- /dev/null +++ b/applications/plugins/totp/services/hmac/sha512.h @@ -0,0 +1,111 @@ +/* Declarations of functions and data types used for SHA512 and SHA384 sum + library functions. + Copyright (C) 2005-2006, 2008-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#pragma once + +#include +#include "u64.h" + +#if HAVE_OPENSSL_SHA512 +#ifndef OPENSSL_API_COMPAT +#define OPENSSL_API_COMPAT 0x10101000L /* FIXME: Use OpenSSL 1.1+ API. */ +#endif +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +enum { SHA384_DIGEST_SIZE = 384 / 8 }; +enum { SHA512_DIGEST_SIZE = 512 / 8 }; + +#if HAVE_OPENSSL_SHA512 +#define GL_OPENSSL_NAME 384 +#include "gl_openssl.h" +#define GL_OPENSSL_NAME 512 +#include "gl_openssl.h" +#else +/* Structure to save state of computation between the single steps. */ +struct sha512_ctx { + u64 state[8]; + + u64 total[2]; + size_t buflen; /* ≥ 0, ≤ 256 */ + u64 buffer[32]; /* 256 bytes; the first buflen bytes are in use */ +}; + +/* Initialize structure containing state of computation. */ +extern void sha512_init_ctx(struct sha512_ctx* ctx); +extern void sha384_init_ctx(struct sha512_ctx* ctx); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is necessary that LEN is a multiple of 128!!! */ +extern void sha512_process_block(const void* buffer, size_t len, struct sha512_ctx* ctx); + +/* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is NOT required that LEN is a multiple of 128. */ +extern void sha512_process_bytes(const void* buffer, size_t len, struct sha512_ctx* ctx); + +/* Process the remaining bytes in the buffer and put result from CTX + in first 64 (48) bytes following RESBUF. The result is always in little + endian byte order, so that a byte-wise output yields to the wanted + ASCII representation of the message digest. */ +extern void* sha512_finish_ctx(struct sha512_ctx* ctx, void* restrict resbuf); +extern void* sha384_finish_ctx(struct sha512_ctx* ctx, void* restrict resbuf); + +/* Put result from CTX in first 64 (48) bytes following RESBUF. The result is + always in little endian byte order, so that a byte-wise output yields + to the wanted ASCII representation of the message digest. + + IMPORTANT: On some systems it is required that RESBUF is correctly + aligned for a 32 bits value. */ +extern void* sha512_read_ctx(const struct sha512_ctx* ctx, void* restrict resbuf); +extern void* sha384_read_ctx(const struct sha512_ctx* ctx, void* restrict resbuf); + +/* Compute SHA512 (SHA384) message digest for LEN bytes beginning at BUFFER. + The result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +extern void* sha512_buffer(const char* buffer, size_t len, void* restrict resblock); +extern void* sha384_buffer(const char* buffer, size_t len, void* restrict resblock); + +#endif + +/* Compute SHA512 (SHA384) message digest for bytes read from STREAM. + STREAM is an open file stream. Regular files are handled more efficiently. + The contents of STREAM from its current position to its end will be read. + The case that the last operation on STREAM was an 'ungetc' is not supported. + The resulting message digest number will be written into the 64 (48) bytes + beginning at RESBLOCK. */ +extern int sha512_stream(FILE* stream, void* resblock); +extern int sha384_stream(FILE* stream, void* resblock); + +#ifdef __cplusplus +} +#endif + +/* + * Hey Emacs! + * Local Variables: + * coding: utf-8 + * End: + */ diff --git a/applications/plugins/totp/services/hmac/u64.c b/applications/plugins/totp/services/hmac/u64.c new file mode 100644 index 000000000..de6257ba0 --- /dev/null +++ b/applications/plugins/totp/services/hmac/u64.c @@ -0,0 +1,20 @@ +/* uint64_t-like operations that work even on hosts lacking uint64_t + + Copyright (C) 2012-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +#define _GL_U64_INLINE _GL_EXTERN_INLINE +#include "u64.h" +typedef int dummy; diff --git a/applications/plugins/totp/services/hmac/u64.h b/applications/plugins/totp/services/hmac/u64.h new file mode 100644 index 000000000..af0bb067b --- /dev/null +++ b/applications/plugins/totp/services/hmac/u64.h @@ -0,0 +1,44 @@ +/* uint64_t-like operations that work even on hosts lacking uint64_t + + Copyright (C) 2006, 2009-2022 Free Software Foundation, Inc. + + This file is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . */ + +/* Written by Paul Eggert. */ + +#pragma once + +#include + +#ifndef _GL_U64_INLINE +#define _GL_U64_INLINE _GL_INLINE +#endif + +/* Return X rotated left by N bits, where 0 < N < 64. */ +#define u64rol(x, n) u64or(u64shl(x, n), u64shr(x, 64 - n)) + +/* Native implementations are trivial. See below for comments on what + these operations do. */ +typedef uint64_t u64; +#define u64hilo(hi, lo) ((u64)(((u64)(hi) << 32) + (lo))) +#define u64init(hi, lo) u64hilo(hi, lo) +#define u64lo(x) ((u64)(x)) +#define u64size(x) u64lo(x) +#define u64lt(x, y) ((x) < (y)) +#define u64and(x, y) ((x) & (y)) +#define u64or(x, y) ((x) | (y)) +#define u64xor(x, y) ((x) ^ (y)) +#define u64plus(x, y) ((x) + (y)) +#define u64shl(x, n) ((x) << (n)) +#define u64shr(x, n) ((x) >> (n)) diff --git a/applications/plugins/totp/services/list/list.c b/applications/plugins/totp/services/list/list.c new file mode 100644 index 000000000..77df1105a --- /dev/null +++ b/applications/plugins/totp/services/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/services/list/list.h b/applications/plugins/totp/services/list/list.h new file mode 100644 index 000000000..771eac565 --- /dev/null +++ b/applications/plugins/totp/services/list/list.h @@ -0,0 +1,24 @@ +#pragma once + +#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. */ diff --git a/applications/plugins/totp/services/timezone_utils/timezone_utils.c b/applications/plugins/totp/services/timezone_utils/timezone_utils.c new file mode 100644 index 000000000..31df3bbba --- /dev/null +++ b/applications/plugins/totp/services/timezone_utils/timezone_utils.c @@ -0,0 +1,16 @@ +#include "timezone_utils.h" + +int32_t timezone_offset_from_hours(float hours) { + return hours * 3600.0f; +} + +uint64_t timezone_offset_apply(uint64_t time, int32_t offset) { + uint64_t for_time_adjusted; + if(offset > 0) { + for_time_adjusted = time - offset; + } else { + for_time_adjusted = time + (-offset); + } + + return for_time_adjusted; +} diff --git a/applications/plugins/totp/services/timezone_utils/timezone_utils.h b/applications/plugins/totp/services/timezone_utils/timezone_utils.h new file mode 100644 index 000000000..0ae829d74 --- /dev/null +++ b/applications/plugins/totp/services/timezone_utils/timezone_utils.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +int32_t timezone_offset_from_hours(float hours); +uint64_t timezone_offset_apply(uint64_t time, int32_t offset); diff --git a/applications/plugins/totp/services/totp/totp.c b/applications/plugins/totp/services/totp/totp.c new file mode 100644 index 000000000..8c51cde38 --- /dev/null +++ b/applications/plugins/totp/services/totp/totp.c @@ -0,0 +1,150 @@ +#include "totp.h" + +#include +#include +#include +#include +#include +#include "../hmac/hmac_sha1.h" +#include "../hmac/hmac_sha256.h" +#include "../hmac/hmac_sha512.h" +#include "../timezone_utils/timezone_utils.h" + +#define UINT64_GET_BYTE(integer, index) ((integer >> (8 * index)) & 0xFF) + +/* + Generates the timeblock for a time in seconds. + + Timeblocks are the amount of intervals in a given time. For example, + if 1,000,000 seconds has passed for 30 second intervals, you would get + 33,333 timeblocks (intervals), where timeblock++ is effectively +30 seconds. + + for_time is a time in seconds to get the current timeblocks + + Returns + timeblock given for_time, using data->interval + error, 0 +*/ +uint64_t totp_timecode(uint8_t interval, uint64_t for_time) { + return for_time / interval; +} + +/* + Converts an integer into an 8 byte array. + + out_bytes is the null-terminated output string already allocated +*/ +void otp_num_to_bytes(uint64_t integer, uint8_t* out_bytes) { + out_bytes[7] = UINT64_GET_BYTE(integer, 0); + out_bytes[6] = UINT64_GET_BYTE(integer, 1); + out_bytes[5] = UINT64_GET_BYTE(integer, 2); + out_bytes[4] = UINT64_GET_BYTE(integer, 3); + out_bytes[3] = UINT64_GET_BYTE(integer, 4); + out_bytes[2] = UINT64_GET_BYTE(integer, 5); + out_bytes[1] = UINT64_GET_BYTE(integer, 6); + out_bytes[0] = UINT64_GET_BYTE(integer, 7); +} + +/* + Generates an OTP (One Time Password). + + input is a number used to generate the OTP + out_str is the null-terminated output string already allocated + + Returns + OTP code if otp code was successfully generated + 0 otherwise +*/ +uint32_t otp_generate( + TOTP_ALGO algo, + uint8_t digits, + const uint8_t* plain_secret, + uint8_t plain_secret_length, + uint64_t input) { + uint8_t* bytes = malloc(8); + memset(bytes, 0, 8); + uint8_t* hmac = malloc(64); + memset(hmac, 0, 64); + + otp_num_to_bytes(input, bytes); + + int hmac_len = (*(algo))(plain_secret, plain_secret_length, bytes, 8, hmac); + if(hmac_len == 0) { + free(hmac); + free(bytes); + return OTP_ERROR; + } + + uint64_t offset = (hmac[hmac_len - 1] & 0xF); + uint64_t i_code = + ((hmac[offset] & 0x7F) << 24 | (hmac[offset + 1] & 0xFF) << 16 | + (hmac[offset + 2] & 0xFF) << 8 | (hmac[offset + 3] & 0xFF)); + i_code %= (uint64_t)pow(10, digits); + + free(hmac); + free(bytes); + return i_code; +} + +/* + Generates a OTP key using the totp algorithm. + + for_time is the time the generated key will be created for + offset is a timeblock adjustment for the generated key + out_str is the null-terminated output string already allocated + + Returns + TOTP code if otp code was successfully generated + 0 otherwise +*/ +uint32_t totp_at( + TOTP_ALGO algo, + uint8_t digits, + const uint8_t* plain_secret, + uint8_t plain_secret_length, + uint64_t for_time, + float timezone, + uint8_t interval) { + uint64_t for_time_adjusted = + timezone_offset_apply(for_time, timezone_offset_from_hours(timezone)); + return otp_generate( + algo, + digits, + plain_secret, + plain_secret_length, + totp_timecode(interval, for_time_adjusted)); +} + +static int totp_algo_sha1( + const uint8_t* key, + uint8_t key_length, + const uint8_t* input, + uint8_t input_length, + uint8_t* output) { + hmac_sha1(key, key_length, input, input_length, output); + return HMAC_SHA1_RESULT_SIZE; +} + +static int totp_algo_sha256( + const uint8_t* key, + uint8_t key_length, + const uint8_t* input, + uint8_t input_length, + uint8_t* output) { + hmac_sha256(key, key_length, input, input_length, output); + return HMAC_SHA256_RESULT_SIZE; +} + +static int totp_algo_sha512( + const uint8_t* key, + uint8_t key_length, + const uint8_t* input, + uint8_t input_length, + uint8_t* output) { + hmac_sha512(key, key_length, input, input_length, output); + return HMAC_SHA512_RESULT_SIZE; +} + +const TOTP_ALGO TOTP_ALGO_SHA1 = (TOTP_ALGO)(&totp_algo_sha1); +const TOTP_ALGO TOTP_ALGO_SHA256 = (TOTP_ALGO)(&totp_algo_sha256); +const TOTP_ALGO TOTP_ALGO_SHA512 = (TOTP_ALGO)(&totp_algo_sha512); diff --git a/applications/plugins/totp/services/totp/totp.h b/applications/plugins/totp/services/totp/totp.h new file mode 100644 index 000000000..31e70e01b --- /dev/null +++ b/applications/plugins/totp/services/totp/totp.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +#define OTP_ERROR (0) + +/* + Must compute HMAC using passed arguments, + output as char array through output. + + key is secret key. + input is input number. + output is an output buffer of the resulting HMAC operation. + + Must return 0 if error, or the length in bytes of the HMAC operation. +*/ +typedef int (*TOTP_ALGO)( + const uint8_t* key, + uint8_t key_length, + const uint8_t* input, + uint8_t input_length, + uint8_t* output); + +/* + Computes HMAC using SHA1 +*/ +extern const TOTP_ALGO TOTP_ALGO_SHA1; + +/* + Computes HMAC using SHA256 +*/ +extern const TOTP_ALGO TOTP_ALGO_SHA256; + +/* + Computes HMAC using SHA512 +*/ +extern const TOTP_ALGO TOTP_ALGO_SHA512; + +/* + Computes TOTP token + Returns: + TOTP token on success + 0 otherwise +*/ +uint32_t totp_at( + TOTP_ALGO algo, + uint8_t digits, + const uint8_t* plain_secret, + uint8_t plain_secret_length, + uint64_t for_time, + float timezone, + uint8_t interval); diff --git a/applications/plugins/totp/services/ui/constants.h b/applications/plugins/totp/services/ui/constants.h new file mode 100644 index 000000000..9caf90c4e --- /dev/null +++ b/applications/plugins/totp/services/ui/constants.h @@ -0,0 +1,6 @@ +#pragma once + +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 +#define SCREEN_WIDTH_CENTER (SCREEN_WIDTH >> 1) +#define SCREEN_HEIGHT_CENTER (SCREEN_HEIGHT >> 1) diff --git a/applications/plugins/totp/services/ui/icons.h b/applications/plugins/totp/services/ui/icons.h new file mode 100644 index 000000000..a9139403f --- /dev/null +++ b/applications/plugins/totp/services/ui/icons.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#define ICON_ARROW_LEFT_8x9_WIDTH 8 +#define ICON_ARROW_LEFT_8x9_HEIGHT 9 +static const uint8_t ICON_ARROW_LEFT_8x9[] = {0x80, 0xe0, 0xf8, 0xfe, 0xff, 0xfe, 0xf8, 0xe0, 0x80}; + +#define ICON_ARROW_RIGHT_8x9_WIDTH 8 +#define ICON_ARROW_RIGHT_8x9_HEIGHT 9 +static const uint8_t ICON_ARROW_RIGHT_8x9[] = + {0x01, 0x07, 0x1f, 0x7f, 0xff, 0x7f, 0x1f, 0x07, 0x01}; diff --git a/applications/plugins/totp/services/ui/ui_controls.c b/applications/plugins/totp/services/ui/ui_controls.c new file mode 100644 index 000000000..269c52294 --- /dev/null +++ b/applications/plugins/totp/services/ui/ui_controls.c @@ -0,0 +1,114 @@ +#include "ui_controls.h" +#include "constants.h" +#include "icons.h" + +#define TEXT_BOX_HEIGHT 13 +#define TEXT_BOX_MARGIN 4 + +void ui_control_text_box_render(Canvas* const canvas, int8_t y, char* text, bool is_selected) { + if(y < -TEXT_BOX_HEIGHT) { + return; + } + + if(is_selected) { + canvas_draw_rframe( + canvas, + TEXT_BOX_MARGIN, + TEXT_BOX_MARGIN + y, + SCREEN_WIDTH - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN, + TEXT_BOX_HEIGHT, + 0); + canvas_draw_rframe( + canvas, + TEXT_BOX_MARGIN - 1, + TEXT_BOX_MARGIN + y - 1, + SCREEN_WIDTH - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN + 2, + TEXT_BOX_HEIGHT + 2, + 1); + } else { + canvas_draw_rframe( + canvas, + TEXT_BOX_MARGIN, + TEXT_BOX_MARGIN + y, + SCREEN_WIDTH - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN, + TEXT_BOX_HEIGHT, + 1); + } + + canvas_draw_str_aligned( + canvas, TEXT_BOX_MARGIN + 2, TEXT_BOX_MARGIN + 3 + y, AlignLeft, AlignTop, text); +} + +void ui_control_select_render(Canvas* const canvas, int8_t y, char* text, bool is_selected) { + if(y < -TEXT_BOX_HEIGHT) { + return; + } + + if(is_selected) { + canvas_draw_rframe( + canvas, + TEXT_BOX_MARGIN, + TEXT_BOX_MARGIN + y, + SCREEN_WIDTH - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN, + TEXT_BOX_HEIGHT, + 0); + canvas_draw_rframe( + canvas, + TEXT_BOX_MARGIN - 1, + TEXT_BOX_MARGIN + y - 1, + SCREEN_WIDTH - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN + 2, + TEXT_BOX_HEIGHT + 2, + 1); + } else { + canvas_draw_rframe( + canvas, + TEXT_BOX_MARGIN, + TEXT_BOX_MARGIN + y, + SCREEN_WIDTH - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN, + TEXT_BOX_HEIGHT, + 1); + } + + canvas_draw_str_aligned( + canvas, SCREEN_WIDTH_CENTER, TEXT_BOX_MARGIN + 3 + y, AlignCenter, AlignTop, text); + canvas_draw_xbm( + canvas, + TEXT_BOX_MARGIN + 2, + TEXT_BOX_MARGIN + 2 + y, + ICON_ARROW_LEFT_8x9_WIDTH, + ICON_ARROW_LEFT_8x9_HEIGHT, + &ICON_ARROW_LEFT_8x9[0]); + canvas_draw_xbm( + canvas, + SCREEN_WIDTH - TEXT_BOX_MARGIN - 10, + TEXT_BOX_MARGIN + 2 + y, + ICON_ARROW_RIGHT_8x9_WIDTH, + ICON_ARROW_RIGHT_8x9_HEIGHT, + &ICON_ARROW_RIGHT_8x9[0]); +} + +void ui_control_button_render( + Canvas* const canvas, + uint8_t x, + int8_t y, + uint8_t width, + uint8_t height, + char* text, + bool is_selected) { + if(y < -height) { + return; + } + + if(is_selected) { + canvas_draw_rbox(canvas, x, y, width, height, 1); + canvas_set_color(canvas, ColorWhite); + } else { + canvas_draw_rframe(canvas, x, y, width, height, 1); + } + + canvas_draw_str_aligned( + canvas, x + (width >> 1), y + (height >> 1) + 1, AlignCenter, AlignCenter, text); + if(is_selected) { + canvas_set_color(canvas, ColorBlack); + } +} diff --git a/applications/plugins/totp/services/ui/ui_controls.h b/applications/plugins/totp/services/ui/ui_controls.h new file mode 100644 index 000000000..f0c8bb6bb --- /dev/null +++ b/applications/plugins/totp/services/ui/ui_controls.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +void ui_control_text_box_render(Canvas* const canvas, int8_t y, char* text, bool is_selected); +void ui_control_button_render( + Canvas* const canvas, + uint8_t x, + int8_t y, + uint8_t width, + uint8_t height, + char* text, + bool is_selected); +void ui_control_select_render(Canvas* const canvas, int8_t y, char* text, bool is_selected); diff --git a/applications/plugins/totp/totp_10px.png b/applications/plugins/totp/totp_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..70ed56d988f87d376ea545a6202375f224308f5a GIT binary patch literal 496 zcmV4Tx04UFWkg-bwVHn0Atq{tFsKKG(rf9fIG{~hv4WtMU-jA2>$HViyyu5%vq%v0O22eI^ zGo4CEE16X(@CqM2r&P^iaYdOI{PX{=j|bO#HzsB^TebbzUh1lRda!Q~^tS(nco&r&)`d_yd0MKwp!+mK|%Bn|D@wjVuRcjn3OA^2%9o-Qp5zEcoqgU zj6)`lAWR(*clPdg@7+&sYr8MNbc1t#-qz+Cz{wPytlri}W57Tka9wArsX9^@wLhN+ z-j0F(3@~_0nKdgPjkwzf6F|>1=kFsx-~eb +#include +#include +#include +#include +#include +#include +#include +#include +#include "services/base32/base32.h" +#include "services/list/list.h" +#include "services/config/config.h" +#include "types/plugin_state.h" +#include "types/token_info.h" +#include "types/plugin_event.h" +#include "types/event_type.h" +#include "types/common.h" +#include "scenes/scene_director.h" + +#define IDLE_TIMEOUT 60000 + +static void render_callback(Canvas* const canvas, void* ctx) { + PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25); + if(plugin_state != NULL && !plugin_state->changing_scene) { + totp_scene_director_render(canvas, plugin_state); + } + + release_mutex((ValueMutex*)ctx, plugin_state); +} + +static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + PluginEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void totp_state_init(PluginState* const plugin_state) { + plugin_state->gui = furi_record_open(RECORD_GUI); + plugin_state->notification = furi_record_open(RECORD_NOTIFICATION); + plugin_state->dialogs = furi_record_open(RECORD_DIALOGS); + totp_config_file_load_base(plugin_state); + + totp_scene_director_init_scenes(plugin_state); + totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL); +} + +static void dispose_plugin_state(PluginState* plugin_state) { + totp_scene_director_deactivate_active_scene(plugin_state); + + totp_scene_director_dispose(plugin_state); + + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_DIALOGS); + + ListNode* node = plugin_state->tokens_list; + ListNode* tmp; + while(node != NULL) { + tmp = node->next; + TokenInfo* tokenInfo = (TokenInfo*)node->data; + token_info_free(tokenInfo); + free(node); + node = tmp; + } + + if(plugin_state->crypto_verify_data != NULL) { + free(plugin_state->crypto_verify_data); + } + free(plugin_state); +} + +int32_t totp_app() { + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + PluginState* plugin_state = malloc(sizeof(PluginState)); + + totp_state_init(plugin_state); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) { + FURI_LOG_E(LOGGING_TAG, "Cannot create mutex\r\n"); + dispose_plugin_state(plugin_state); + return 255; + } + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, &state_mutex); + view_port_input_callback_set(view_port, input_callback, event_queue); + + // Open GUI and register view_port + gui_add_view_port(plugin_state->gui, view_port, GuiLayerFullscreen); + + PluginEvent event; + bool processing = true; + uint32_t last_user_interaction_time = furi_get_tick(); + while(processing) { + if(plugin_state->changing_scene) continue; + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + + PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex); + + if(event_status == FuriStatusOk) { + if(event.type == EventTypeKey) { + last_user_interaction_time = furi_get_tick(); + } + + processing = totp_scene_director_handle_event(&event, plugin_state); + } else if( + plugin_state->current_scene != TotpSceneAuthentication && + furi_get_tick() - last_user_interaction_time > IDLE_TIMEOUT) { + totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL); + } + + view_port_update(view_port); + release_mutex(&state_mutex, plugin_state); + } + + view_port_enabled_set(view_port, false); + gui_remove_view_port(plugin_state->gui, view_port); + view_port_free(view_port); + furi_message_queue_free(event_queue); + delete_mutex(&state_mutex); + dispose_plugin_state(plugin_state); + return 0; +} diff --git a/applications/plugins/totp/types/common.h b/applications/plugins/totp/types/common.h new file mode 100644 index 000000000..df8f6cc2d --- /dev/null +++ b/applications/plugins/totp/types/common.h @@ -0,0 +1,4 @@ +#pragma once + +#define LOGGING_TAG "TOTP APP" +#define CRYPTO_KEY_SLOT 2 diff --git a/applications/plugins/totp/types/event_type.h b/applications/plugins/totp/types/event_type.h new file mode 100644 index 000000000..ed890caf1 --- /dev/null +++ b/applications/plugins/totp/types/event_type.h @@ -0,0 +1,7 @@ +#pragma once +#include + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; diff --git a/applications/plugins/totp/types/plugin_event.h b/applications/plugins/totp/types/plugin_event.h new file mode 100644 index 000000000..76c22af59 --- /dev/null +++ b/applications/plugins/totp/types/plugin_event.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include +#include "event_type.h" + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; diff --git a/applications/plugins/totp/types/plugin_state.h b/applications/plugins/totp/types/plugin_state.h new file mode 100644 index 000000000..3f2b30ed6 --- /dev/null +++ b/applications/plugins/totp/types/plugin_state.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include "../services/list/list.h" +#include "../scenes/totp_scenes_enum.h" + +#define TOTP_IV_SIZE 16 + +typedef struct { + Scene current_scene; + void* current_scene_state; + bool changing_scene; + NotificationApp* notification; + DialogsApp* dialogs; + Gui* gui; + + float timezone_offset; + ListNode* tokens_list; + bool token_list_loaded; + uint8_t tokens_count; + + uint8_t* crypto_verify_data; + uint8_t crypto_verify_data_length; + uint8_t iv[TOTP_IV_SIZE]; + uint8_t base_iv[TOTP_IV_SIZE]; +} PluginState; diff --git a/applications/plugins/totp/types/token_info.c b/applications/plugins/totp/types/token_info.c new file mode 100644 index 000000000..6596cd510 --- /dev/null +++ b/applications/plugins/totp/types/token_info.c @@ -0,0 +1,61 @@ +#include +#include +#include "token_info.h" +#include "stdlib.h" +#include "common.h" +#include "../services/base32/base32.h" + +TokenInfo* token_info_alloc() { + TokenInfo* tokenInfo = malloc(sizeof(TokenInfo)); + tokenInfo->algo = SHA1; + tokenInfo->digits = TOTP_6_DIGITS; + return tokenInfo; +} + +void token_info_free(TokenInfo* token_info) { + if(token_info == NULL) return; + free(token_info->name); + free(token_info->token); + free(token_info); +} + +void token_info_set_secret( + TokenInfo* token_info, + const char* base32_token_secret, + uint8_t token_secret_length, + uint8_t* iv) { + uint8_t* plain_secret = malloc(token_secret_length); + int plain_secret_length = + base32_decode((uint8_t*)base32_token_secret, plain_secret, token_secret_length); + token_info->token_length = plain_secret_length; + + size_t remain = token_info->token_length % 16; + if(remain) { + token_info->token_length = token_info->token_length - remain + 16; + uint8_t* plain_secret_aligned = malloc(token_info->token_length); + memcpy(plain_secret_aligned, plain_secret, plain_secret_length); + memset(plain_secret, 0, plain_secret_length); + free(plain_secret); + plain_secret = plain_secret_aligned; + } + + token_info->token = malloc(token_info->token_length); + + furi_hal_crypto_store_load_key(CRYPTO_KEY_SLOT, iv); + furi_hal_crypto_encrypt(plain_secret, token_info->token, token_info->token_length); + furi_hal_crypto_store_unload_key(CRYPTO_KEY_SLOT); + + memset(plain_secret, 0, token_info->token_length); + free(plain_secret); +} + +uint8_t token_info_get_digits_count(TokenInfo* token_info) { + switch(token_info->digits) { + case TOTP_6_DIGITS: + return 6; + case TOTP_8_DIGITS: + return 8; + } + + return 6; +} diff --git a/applications/plugins/totp/types/token_info.h b/applications/plugins/totp/types/token_info.h new file mode 100644 index 000000000..2d531ec89 --- /dev/null +++ b/applications/plugins/totp/types/token_info.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +typedef enum { SHA1, SHA256, SHA512 } TokenHashAlgo; + +typedef enum { TOTP_6_DIGITS, TOTP_8_DIGITS } TokenDigitsCount; + +typedef struct { + uint8_t* token; + uint8_t token_length; + char* name; + TokenHashAlgo algo; + TokenDigitsCount digits; +} TokenInfo; + +TokenInfo* token_info_alloc(); +void token_info_free(TokenInfo* token_info); +void token_info_set_secret( + TokenInfo* token_info, + const char* base32_token_secret, + uint8_t token_secret_length, + uint8_t* iv); +uint8_t token_info_get_digits_count(TokenInfo* token_info); diff --git a/applications/plugins/usbkeyboard/usb_hid.c b/applications/plugins/usbkeyboard/usb_hid.c index c2344565b..60acb6e32 100644 --- a/applications/plugins/usbkeyboard/usb_hid.c +++ b/applications/plugins/usbkeyboard/usb_hid.c @@ -72,8 +72,10 @@ UsbHid* usb_hid_app_alloc() { app->submenu, "Dirpad", UsbHidSubmenuIndexDirpad, usb_hid_submenu_callback, app); submenu_add_item( app->submenu, "Keyboard", UsbHidSubmenuIndexKeyboard, usb_hid_submenu_callback, app); - submenu_add_item(app->submenu, "Media", UsbHidSubmenuIndexMedia, usb_hid_submenu_callback, app); - submenu_add_item(app->submenu, "Mouse", UsbHidSubmenuIndexMouse, usb_hid_submenu_callback, app); + submenu_add_item( + app->submenu, "Media", UsbHidSubmenuIndexMedia, usb_hid_submenu_callback, app); + submenu_add_item( + app->submenu, "Mouse", UsbHidSubmenuIndexMouse, usb_hid_submenu_callback, app); view_set_previous_callback(submenu_get_view(app->submenu), usb_hid_exit); view_dispatcher_add_view( app->view_dispatcher, UsbHidViewSubmenu, submenu_get_view(app->submenu)); @@ -101,17 +103,21 @@ UsbHid* usb_hid_app_alloc() { view_set_previous_callback( usb_hid_keyboard_get_view(app->usb_hid_keyboard), usb_hid_exit_confirm_view); view_dispatcher_add_view( - app->view_dispatcher, UsbHidViewKeyboard, usb_hid_keyboard_get_view(app->usb_hid_keyboard)); + app->view_dispatcher, + UsbHidViewKeyboard, + usb_hid_keyboard_get_view(app->usb_hid_keyboard)); // Media view app->usb_hid_media = usb_hid_media_alloc(); - view_set_previous_callback(usb_hid_media_get_view(app->usb_hid_media), usb_hid_exit_confirm_view); + view_set_previous_callback( + usb_hid_media_get_view(app->usb_hid_media), usb_hid_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, UsbHidViewMedia, usb_hid_media_get_view(app->usb_hid_media)); // Mouse view app->usb_hid_mouse = usb_hid_mouse_alloc(); - view_set_previous_callback(usb_hid_mouse_get_view(app->usb_hid_mouse), usb_hid_exit_confirm_view); + view_set_previous_callback( + usb_hid_mouse_get_view(app->usb_hid_mouse), usb_hid_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, UsbHidViewMouse, usb_hid_mouse_get_view(app->usb_hid_mouse)); @@ -157,14 +163,14 @@ int32_t usb_hid_app(void* p) { // Switch profile to Hid UsbHid* app = usb_hid_app_alloc(); - FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); + FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); furi_hal_usb_unlock(); furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true); - view_dispatcher_run(app->view_dispatcher); - - // Change back profile - furi_hal_usb_set_config(usb_mode_prev, NULL); + view_dispatcher_run(app->view_dispatcher); + + // Change back profile + furi_hal_usb_set_config(usb_mode_prev, NULL); usb_hid_app_free(app); return 0;