diff --git a/applications/main/nfc/helpers/felica_auth.c b/applications/main/nfc/helpers/felica_auth.c new file mode 100644 index 0000000000..a8cd0929ba --- /dev/null +++ b/applications/main/nfc/helpers/felica_auth.c @@ -0,0 +1,21 @@ +#include "felica_auth.h" + +FelicaAuthenticationContext* felica_auth_alloc() { + FelicaAuthenticationContext* instance = malloc(sizeof(FelicaAuthenticationContext)); + memset(instance->card_key.data, 0, FELICA_DATA_BLOCK_SIZE); + instance->skip_auth = true; + return instance; +} + +void felica_auth_free(FelicaAuthenticationContext* instance) { + furi_assert(instance); + free(instance); +} + +void felica_auth_reset(FelicaAuthenticationContext* instance) { + furi_assert(instance); + memset(instance->card_key.data, 0, FELICA_DATA_BLOCK_SIZE); + instance->skip_auth = true; + instance->auth_status.external = 0; + instance->auth_status.internal = 0; +} \ No newline at end of file diff --git a/applications/main/nfc/helpers/felica_auth.h b/applications/main/nfc/helpers/felica_auth.h new file mode 100644 index 0000000000..3d99f1f9cd --- /dev/null +++ b/applications/main/nfc/helpers/felica_auth.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +FelicaAuthenticationContext* felica_auth_alloc(); + +void felica_auth_free(FelicaAuthenticationContext* instance); + +void felica_auth_reset(FelicaAuthenticationContext* instance); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index affa33b865..b0660f3e62 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -7,6 +7,11 @@ #include "../nfc_protocol_support_common.h" #include "../nfc_protocol_support_gui_common.h" +#include "../nfc_protocol_support_unlock_helper.h" + +enum { + SubmenuIndexUnlock = SubmenuIndexCommonMax, +}; static void nfc_scene_info_on_enter_felica(NfcApp* instance) { const NfcDevice* device = instance->nfc_device; @@ -18,6 +23,35 @@ static void nfc_scene_info_on_enter_felica(NfcApp* instance) { temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_felica_info(data, NfcProtocolFormatTypeFull, temp_str); + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 48, furi_string_get_cstr(temp_str)); + + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "More", + nfc_protocol_support_common_widget_callback, + instance); + furi_string_free(temp_str); +} + +static bool nfc_scene_info_on_event_felica(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMoreInfo); + return true; + } + + return false; +} + +static void nfc_scene_more_info_on_enter_felica(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica); + + FuriString* temp_str = furi_string_alloc(); + + nfc_render_felica_dump(data, temp_str); + widget_add_text_scroll_element( instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); @@ -29,29 +63,75 @@ static NfcCommand nfc_scene_read_poller_callback_felica(NfcGenericEvent event, v NfcApp* instance = context; const FelicaPollerEvent* felica_event = event.event_data; + NfcCommand command = NfcCommandContinue; if(felica_event->type == FelicaPollerEventTypeReady) { nfc_device_set_data( instance->nfc_device, NfcProtocolFelica, nfc_poller_get_data(instance->poller)); view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); - return NfcCommandStop; + command = NfcCommandStop; + } else if( + felica_event->type == FelicaPollerEventTypeError || + felica_event->type == FelicaPollerEventTypeIncomplete) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolFelica, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventPollerIncomplete); + command = NfcCommandStop; + } else if(felica_event->type == FelicaPollerEventTypeRequestAuthContext) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); + FelicaAuthenticationContext* ctx = felica_event->data->auth_context; + ctx->skip_auth = instance->felica_auth->skip_auth; + memcpy(ctx->card_key.data, instance->felica_auth->card_key.data, FELICA_DATA_BLOCK_SIZE); } - return NfcCommandContinue; + return command; } static void nfc_scene_read_on_enter_felica(NfcApp* instance) { + nfc_unlock_helper_setup_from_state(instance); nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_felica, instance); } +bool nfc_scene_read_on_event_felica(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventCardDetected) { + nfc_unlock_helper_card_detected_handler(instance); + } else if(event.event == NfcCustomEventPollerIncomplete) { + notification_message(instance->notifications, &sequence_semi_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + } + return true; +} + static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) { const NfcDevice* device = instance->nfc_device; const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica); FuriString* temp_str = furi_string_alloc(); - furi_string_cat_printf( - temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); - nfc_render_felica_info(data, NfcProtocolFormatTypeShort, temp_str); + + if(!scene_manager_has_previous_scene(instance->scene_manager, NfcSceneFelicaUnlockWarn)) { + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_felica_info(data, NfcProtocolFormatTypeShort, temp_str); + } else { + bool all_unlocked = data->blocks_read == data->blocks_total; + furi_string_cat_printf( + temp_str, + "\e#%s\n", + all_unlocked ? "All Blocks Are Unlocked" : "Some Blocks Are Locked"); + nfc_render_felica_idm(data, NfcProtocolFormatTypeShort, temp_str); + uint8_t* ck_data = instance->felica_auth->card_key.data; + furi_string_cat_printf(temp_str, "Key:"); + for(uint8_t i = 0; i < 7; i++) { + furi_string_cat_printf(temp_str, " %02X", ck_data[i]); + if(i == 6) furi_string_cat_printf(temp_str, "..."); + } + nfc_render_felica_blocks_count(data, temp_str, false); + } + felica_auth_reset(instance->felica_auth); widget_add_text_scroll_element( instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); @@ -74,23 +154,50 @@ static void nfc_scene_emulate_on_enter_felica(NfcApp* instance) { nfc_listener_start(instance->listener, NULL, NULL); } +static void nfc_scene_read_menu_on_enter_felica(NfcApp* instance) { + const FelicaData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolFelica); + if(data->blocks_read != data->blocks_total) { + submenu_add_item( + instance->submenu, + "Unlock", + SubmenuIndexUnlock, + nfc_protocol_support_common_submenu_callback, + instance); + } +} + +static bool nfc_scene_read_menu_on_event_felica(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexUnlock) { + scene_manager_next_scene(instance->scene_manager, NfcSceneFelicaKeyInput); + return true; + } + } + return false; +} + const NfcProtocolSupportBase nfc_protocol_support_felica = { .features = NfcProtocolFeatureEmulateUid, .scene_info = { .on_enter = nfc_scene_info_on_enter_felica, + .on_event = nfc_scene_info_on_event_felica, + }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_felica, .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_read = { .on_enter = nfc_scene_read_on_enter_felica, - .on_event = nfc_protocol_support_common_on_event_empty, + .on_event = nfc_scene_read_on_event_felica, }, .scene_read_menu = { - .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_protocol_support_common_on_event_empty, + .on_enter = nfc_scene_read_menu_on_enter_felica, + .on_event = nfc_scene_read_menu_on_event_felica, }, .scene_read_success = { diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c index 3142b2c6db..6c57fb24b6 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c @@ -1,19 +1,107 @@ #include "felica_render.h" -void nfc_render_felica_info( +void nfc_render_felica_blocks_count( + const FelicaData* data, + FuriString* str, + bool render_auth_notification) { + furi_string_cat_printf(str, "\nBlocks Read: %u/%u", data->blocks_read, data->blocks_total); + if(render_auth_notification && data->blocks_read != data->blocks_total) { + furi_string_cat_printf(str, "\nAuth-protected blocks!"); + } +} + +void nfc_render_felica_idm( const FelicaData* data, NfcProtocolFormatType format_type, FuriString* str) { - furi_string_cat_printf(str, "IDm:"); + furi_string_cat_printf(str, (format_type == NfcProtocolFormatTypeFull) ? "IDm:\n" : "IDm:"); for(size_t i = 0; i < FELICA_IDM_SIZE; i++) { - furi_string_cat_printf(str, " %02X", data->idm.data[i]); + furi_string_cat_printf( + str, + (format_type == NfcProtocolFormatTypeFull) ? "%02X " : " %02X", + data->idm.data[i]); + } +} + +void nfc_render_felica_info( + const FelicaData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + if(format_type == NfcProtocolFormatTypeFull) { + furi_string_cat_printf(str, "Tech: JIS X 6319-4,\nISO 18092 [NFC-F]\n"); } + nfc_render_felica_idm(data, format_type, str); + if(format_type == NfcProtocolFormatTypeFull) { - furi_string_cat_printf(str, "\nPMm:"); + furi_string_cat_printf(str, "\nPMm:\n"); for(size_t i = 0; i < FELICA_PMM_SIZE; ++i) { - furi_string_cat_printf(str, " %02X", data->pmm.data[i]); + furi_string_cat_printf(str, "%02X ", data->pmm.data[i]); + } + } + nfc_render_felica_blocks_count(data, str, true); +} + +static void nfc_render_felica_block_name( + const char* name, + FuriString* str, + uint8_t prefix_separator_cnt, + uint8_t suffix_separator_cnt) { + for(uint8_t i = 0; i < prefix_separator_cnt; i++) { + furi_string_cat_printf(str, ":"); + } + furi_string_cat_printf(str, "[ %s ]", name); + for(uint8_t i = 0; i < suffix_separator_cnt; i++) { + furi_string_cat_printf(str, ":"); + } +} + +static void nfc_render_felica_block_data(const FelicaBlock* block, FuriString* str) { + furi_string_cat_printf(str, "\nSF1=%02X; SF2=%02X\n", block->SF1, block->SF2); + for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { + if((j != 0) && (j % 8 == 0)) furi_string_cat_printf(str, "\n"); + furi_string_cat_printf(str, "%02X ", block->data[j]); + } + furi_string_cat_printf(str, "\n"); +} + +static void nfc_render_felica_block( + const FelicaBlock* block, + FuriString* str, + const char* name, + uint8_t prefix_separator_cnt, + uint8_t suffix_separator_cnt) { + nfc_render_felica_block_name(name, str, prefix_separator_cnt, suffix_separator_cnt); + nfc_render_felica_block_data(block, str); +} + +void nfc_render_felica_dump(const FelicaData* data, FuriString* str) { + FuriString* name = furi_string_alloc(); + for(size_t i = 0; i < 14; i++) { + furi_string_printf(name, "S_PAD%d", i); + uint8_t suf_cnt = 18; + if(i == 1) { + suf_cnt = 19; + } else if((i == 10) || (i == 12) || (i == 13)) { + suf_cnt = 16; } + nfc_render_felica_block( + &data->data.fs.spad[i], str, furi_string_get_cstr(name), 20, suf_cnt); } + furi_string_free(name); + nfc_render_felica_block(&data->data.fs.reg, str, "REG", 23, 23); + nfc_render_felica_block(&data->data.fs.rc, str, "RC", 25, 25); + nfc_render_felica_block(&data->data.fs.mac, str, "MAC", 23, 23); + nfc_render_felica_block(&data->data.fs.id, str, "ID", 25, 25); + nfc_render_felica_block(&data->data.fs.d_id, str, "D_ID", 22, 24); + nfc_render_felica_block(&data->data.fs.ser_c, str, "SER_C", 20, 21); + nfc_render_felica_block(&data->data.fs.sys_c, str, "SYS_C", 20, 21); + nfc_render_felica_block(&data->data.fs.ckv, str, "CKV", 23, 23); + nfc_render_felica_block(&data->data.fs.ck, str, "CK", 25, 25); + nfc_render_felica_block(&data->data.fs.mc, str, "MC", 25, 24); + nfc_render_felica_block(&data->data.fs.wcnt, str, "WCNT", 22, 20); + nfc_render_felica_block(&data->data.fs.mac_a, str, "MAC_A", 20, 20); + nfc_render_felica_block(&data->data.fs.state, str, "STATE", 20, 21); + nfc_render_felica_block(&data->data.fs.crc_check, str, "CRC_CHCK", 15, 17); } diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h index 6d9816fc66..3d32e8d14e 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h @@ -4,7 +4,19 @@ #include "../nfc_protocol_support_render_common.h" +void nfc_render_felica_blocks_count( + const FelicaData* data, + FuriString* str, + bool render_auth_notification); + void nfc_render_felica_info( const FelicaData* data, NfcProtocolFormatType format_type, FuriString* str); + +void nfc_render_felica_dump(const FelicaData* data, FuriString* str); + +void nfc_render_felica_idm( + const FelicaData* data, + NfcProtocolFormatType format_type, + FuriString* str); \ No newline at end of file diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index bd2015889d..cd16374bc8 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -8,6 +8,7 @@ #include "../nfc_protocol_support_common.h" #include "../nfc_protocol_support_gui_common.h" +#include "../nfc_protocol_support_unlock_helper.h" enum { SubmenuIndexUnlock = SubmenuIndexCommonMax, @@ -157,48 +158,15 @@ static NfcCommand return NfcCommandContinue; } -enum { - NfcSceneMfUltralightReadMenuStateCardSearch, - NfcSceneMfUltralightReadMenuStateCardFound, -}; - -static void nfc_scene_read_setup_view(NfcApp* instance) { - Popup* popup = instance->popup; - popup_reset(popup); - uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneRead); - - if(state == NfcSceneMfUltralightReadMenuStateCardSearch) { - popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); - popup_set_header(instance->popup, "Unlocking", 97, 15, AlignCenter, AlignTop); - popup_set_text( - instance->popup, "Hold card next\nto Flipper's back", 94, 27, AlignCenter, AlignTop); - } else { - popup_set_header(instance->popup, "Don't move", 85, 27, AlignCenter, AlignTop); - popup_set_icon(instance->popup, 12, 20, &A_Loading_24); - } - - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); -} - static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) { - bool unlocking = - scene_manager_has_previous_scene(instance->scene_manager, NfcSceneMfUltralightUnlockWarn); - - uint32_t state = unlocking ? NfcSceneMfUltralightReadMenuStateCardSearch : - NfcSceneMfUltralightReadMenuStateCardFound; - - scene_manager_set_scene_state(instance->scene_manager, NfcSceneRead, state); - - nfc_scene_read_setup_view(instance); + nfc_unlock_helper_setup_from_state(instance); nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_ultralight, instance); } bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventCardDetected) { - scene_manager_set_scene_state( - instance->scene_manager, NfcSceneRead, NfcSceneMfUltralightReadMenuStateCardFound); - nfc_scene_read_setup_view(instance); + nfc_unlock_helper_card_detected_handler(instance); } else if((event.event == NfcCustomEventPollerIncomplete)) { notification_message(instance->notifications, &sequence_semi_success); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.c new file mode 100644 index 0000000000..f1d504d248 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.c @@ -0,0 +1,38 @@ +#include "nfc_protocol_support_unlock_helper.h" + +static void nfc_scene_read_setup_view(NfcApp* instance) { + Popup* popup = instance->popup; + popup_reset(popup); + uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneRead); + + if(state == NfcSceneReadMenuStateCardSearch) { + popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); + popup_set_header(instance->popup, "Unlocking", 97, 15, AlignCenter, AlignTop); + popup_set_text( + instance->popup, "Hold card next\nto Flipper's back", 94, 27, AlignCenter, AlignTop); + } else { + popup_set_header(instance->popup, "Don't move", 85, 27, AlignCenter, AlignTop); + popup_set_icon(instance->popup, 12, 20, &A_Loading_24); + } + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + +void nfc_unlock_helper_setup_from_state(NfcApp* instance) { + bool unlocking = + scene_manager_has_previous_scene( + instance->scene_manager, NfcSceneMfUltralightUnlockWarn) || + scene_manager_has_previous_scene(instance->scene_manager, NfcSceneFelicaUnlockWarn); + + uint32_t state = unlocking ? NfcSceneReadMenuStateCardSearch : NfcSceneReadMenuStateCardFound; + + scene_manager_set_scene_state(instance->scene_manager, NfcSceneRead, state); + + nfc_scene_read_setup_view(instance); +} + +void nfc_unlock_helper_card_detected_handler(NfcApp* instance) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneRead, NfcSceneReadMenuStateCardFound); + nfc_scene_read_setup_view(instance); +} \ No newline at end of file diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.h new file mode 100644 index 0000000000..65da332402 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.h @@ -0,0 +1,9 @@ +#include "nfc/nfc_app_i.h" + +typedef enum { + NfcSceneReadMenuStateCardSearch, + NfcSceneReadMenuStateCardFound, +} NfcSceneUnlockReadState; + +void nfc_unlock_helper_setup_from_state(NfcApp* instance); +void nfc_unlock_helper_card_detected_handler(NfcApp* instance); \ No newline at end of file diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index 02f852e346..d0557de2af 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -50,6 +50,7 @@ NfcApp* nfc_app_alloc(void) { instance->nfc = nfc_alloc(); + instance->felica_auth = felica_auth_alloc(); instance->mf_ul_auth = mf_ultralight_auth_alloc(); instance->slix_unlock = slix_unlock_alloc(); instance->mfc_key_cache = mf_classic_key_cache_alloc(); @@ -141,6 +142,7 @@ void nfc_app_free(NfcApp* instance) { nfc_free(instance->nfc); + felica_auth_free(instance->felica_auth); mf_ultralight_auth_free(instance->mf_ul_auth); slix_unlock_free(instance->slix_unlock); mf_classic_key_cache_free(instance->mfc_key_cache); diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 706815e623..64f7fc6e7b 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -32,6 +32,7 @@ #include "helpers/mfkey32_logger.h" #include "helpers/mf_classic_key_cache.h" #include "helpers/nfc_supported_cards.h" +#include "helpers/felica_auth.h" #include "helpers/slix_unlock.h" #include @@ -129,6 +130,7 @@ struct NfcApp { NfcScanner* scanner; NfcListener* listener; + FelicaAuthenticationContext* felica_auth; MfUltralightAuth* mf_ul_auth; SlixUnlock* slix_unlock; NfcMfClassicDictAttackContext nfc_dict_context; diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 8a5e29ff39..3017d16a40 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -33,6 +33,8 @@ ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu) ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn) ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput) ADD_SCENE(nfc, mf_ultralight_capture_pass, MfUltralightCapturePass) +ADD_SCENE(nfc, felica_key_input, FelicaKeyInput) +ADD_SCENE(nfc, felica_unlock_warn, FelicaUnlockWarn) ADD_SCENE(nfc, mf_desfire_more_info, MfDesfireMoreInfo) ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp) diff --git a/applications/main/nfc/scenes/nfc_scene_felica_key_input.c b/applications/main/nfc/scenes/nfc_scene_felica_key_input.c new file mode 100644 index 0000000000..b04f12dae8 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_felica_key_input.c @@ -0,0 +1,46 @@ +#include "../nfc_app_i.h" + +void nfc_scene_felica_key_input_byte_input_callback(void* context) { + NfcApp* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); +} + +void nfc_scene_felica_key_input_on_enter(void* context) { + NfcApp* nfc = context; + + // Setup view + ByteInput* byte_input = nfc->byte_input; + byte_input_set_header_text(byte_input, "Enter key in hex"); + byte_input_set_result_callback( + byte_input, + nfc_scene_felica_key_input_byte_input_callback, + NULL, + nfc, + nfc->felica_auth->card_key.data, + FELICA_DATA_BLOCK_SIZE); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); +} + +bool nfc_scene_felica_key_input_on_event(void* context, SceneManagerEvent event) { + NfcApp* nfc = context; + UNUSED(event); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventByteInputDone) { + nfc->felica_auth->skip_auth = false; + scene_manager_next_scene(nfc->scene_manager, NfcSceneFelicaUnlockWarn); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_felica_key_input_on_exit(void* context) { + NfcApp* nfc = context; + + // Clear view + byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(nfc->byte_input, ""); +} diff --git a/applications/main/nfc/scenes/nfc_scene_felica_unlock_warn.c b/applications/main/nfc/scenes/nfc_scene_felica_unlock_warn.c new file mode 100644 index 0000000000..15b61dfa51 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_felica_unlock_warn.c @@ -0,0 +1,59 @@ +#include "../nfc_app_i.h" + +void nfc_scene_felica_unlock_warn_dialog_callback(DialogExResult result, void* context) { + NfcApp* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); +} + +void nfc_scene_felica_unlock_warn_on_enter(void* context) { + NfcApp* nfc = context; + + const char* message = "Risky Action!"; + DialogEx* dialog_ex = nfc->dialog_ex; + dialog_ex_set_context(dialog_ex, nfc); + dialog_ex_set_result_callback(dialog_ex, nfc_scene_felica_unlock_warn_dialog_callback); + + dialog_ex_set_header(dialog_ex, message, 64, 0, AlignCenter, AlignTop); + + FuriString* str = furi_string_alloc(); + furi_string_cat_printf(str, "Unlock with key: "); + for(uint8_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i++) + furi_string_cat_printf(str, "%02X ", nfc->felica_auth->card_key.data[i]); + furi_string_cat_printf(str, "?"); + + nfc_text_store_set(nfc, furi_string_get_cstr(str)); + furi_string_free(str); + + dialog_ex_set_text(dialog_ex, nfc->text_store, 0, 12, AlignLeft, AlignTop); + + dialog_ex_set_left_button_text(dialog_ex, "Cancel"); + dialog_ex_set_right_button_text(dialog_ex, "Unlock"); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx); +} + +bool nfc_scene_felica_unlock_warn_on_event(void* context, SceneManagerEvent event) { + NfcApp* nfc = context; + UNUSED(event); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DialogExResultRight) { + nfc->felica_auth->skip_auth = false; + scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); + consumed = true; + } else if(event.event == DialogExResultLeft) { + scene_manager_previous_scene(nfc->scene_manager); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_felica_unlock_warn_on_exit(void* context) { + NfcApp* nfc = context; + + dialog_ex_reset(nfc->dialog_ex); + nfc_text_store_clear(nfc); +} diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 82317918bd..f047101e6c 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -24,6 +24,7 @@ env.Append( File("protocols/mf_desfire/mf_desfire.h"), File("protocols/slix/slix.h"), File("protocols/st25tb/st25tb.h"), + File("protocols/felica/felica.h"), # Pollers File("protocols/iso14443_3a/iso14443_3a_poller.h"), File("protocols/iso14443_3b/iso14443_3b_poller.h"), @@ -33,6 +34,7 @@ env.Append( File("protocols/mf_classic/mf_classic_poller.h"), File("protocols/mf_desfire/mf_desfire_poller.h"), File("protocols/st25tb/st25tb_poller.h"), + File("protocols/felica/felica_poller.h"), # Listeners File("protocols/iso14443_3a/iso14443_3a_listener.h"), File("protocols/iso14443_4a/iso14443_4a_listener.h"), diff --git a/lib/nfc/protocols/felica/felica.c b/lib/nfc/protocols/felica/felica.c index 7de1310bf9..bc47e06422 100644 --- a/lib/nfc/protocols/felica/felica.c +++ b/lib/nfc/protocols/felica/felica.c @@ -13,6 +13,14 @@ static const uint32_t felica_data_format_version = 1; +/** @brief This is used in felica_prepare_first_block to define which + * type of block needs to be prepared. +*/ +typedef enum { + FelicaMACTypeRead, + FelicaMACTypeWrite, +} FelicaMACType; + const NfcDeviceBase nfc_device_felica = { .protocol_name = FELICA_PROTOCOL_NAME, .alloc = (NfcDeviceAlloc)felica_alloc, @@ -35,18 +43,18 @@ FelicaData* felica_alloc(void) { } void felica_free(FelicaData* data) { - furi_assert(data); - + furi_check(data); free(data); } void felica_reset(FelicaData* data) { + furi_check(data); memset(data, 0, sizeof(FelicaData)); } void felica_copy(FelicaData* data, const FelicaData* other) { - furi_assert(data); - furi_assert(other); + furi_check(data); + furi_check(other); *data = *other; } @@ -59,7 +67,7 @@ bool felica_verify(FelicaData* data, const FuriString* device_type) { } bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { - furi_assert(data); + furi_check(data); bool parsed = false; @@ -77,13 +85,32 @@ bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { break; parsed = true; + uint32_t blocks_total = 0; + uint32_t blocks_read = 0; + if(!flipper_format_read_uint32(ff, "Blocks total", &blocks_total, 1)) break; + if(!flipper_format_read_uint32(ff, "Blocks read", &blocks_read, 1)) break; + data->blocks_total = (uint8_t)blocks_total; + data->blocks_read = (uint8_t)blocks_read; + + FuriString* temp_str = furi_string_alloc(); + for(uint8_t i = 0; i < data->blocks_total; i++) { + furi_string_printf(temp_str, "Block %d", i); + if(!flipper_format_read_hex( + ff, + furi_string_get_cstr(temp_str), + (&data->data.dump[i * sizeof(FelicaBlock)]), + sizeof(FelicaBlock))) { + parsed = false; + break; + } + } } while(false); return parsed; } bool felica_save(const FelicaData* data, FlipperFormat* ff) { - furi_assert(data); + furi_check(data); bool saved = false; @@ -98,15 +125,33 @@ bool felica_save(const FelicaData* data, FlipperFormat* ff) { ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE)) break; + uint32_t blocks_total = data->blocks_total; + uint32_t blocks_read = data->blocks_read; + if(!flipper_format_write_uint32(ff, "Blocks total", &blocks_total, 1)) break; + if(!flipper_format_write_uint32(ff, "Blocks read", &blocks_read, 1)) break; + saved = true; + FuriString* temp_str = furi_string_alloc(); + for(uint8_t i = 0; i < blocks_total; i++) { + furi_string_printf(temp_str, "Block %d", i); + if(!flipper_format_write_hex( + ff, + furi_string_get_cstr(temp_str), + (&data->data.dump[i * sizeof(FelicaBlock)]), + sizeof(FelicaBlock))) { + saved = false; + break; + } + } + furi_string_free(temp_str); } while(false); return saved; } bool felica_is_equal(const FelicaData* data, const FelicaData* other) { - furi_assert(data); - furi_assert(other); + furi_check(data); + furi_check(other); return memcmp(data, other, sizeof(FelicaData)) == 0; } @@ -119,7 +164,7 @@ const char* felica_get_device_name(const FelicaData* data, NfcDeviceNameType nam } const uint8_t* felica_get_uid(const FelicaData* data, size_t* uid_len) { - furi_assert(data); + furi_check(data); // Consider Manufacturer ID as UID if(uid_len) { @@ -130,7 +175,7 @@ const uint8_t* felica_get_uid(const FelicaData* data, size_t* uid_len) { } bool felica_set_uid(FelicaData* data, const uint8_t* uid, size_t uid_len) { - furi_assert(data); + furi_check(data); // Consider Manufacturer ID as UID const bool uid_valid = uid_len == FELICA_IDM_SIZE; @@ -145,3 +190,149 @@ FelicaData* felica_get_base_data(const FelicaData* data) { UNUSED(data); furi_crash("No base data"); } + +static void felica_reverse_copy_block(const uint8_t* array, uint8_t* reverse_array) { + furi_assert(array); + furi_assert(reverse_array); + + for(int i = 0; i < 8; i++) { + reverse_array[i] = array[7 - i]; + } +} + +void felica_calculate_session_key( + mbedtls_des3_context* ctx, + const uint8_t* ck, + const uint8_t* rc, + uint8_t* out) { + furi_check(ctx); + furi_check(ck); + furi_check(rc); + furi_check(out); + + uint8_t iv[8]; + memset(iv, 0, 8); + + uint8_t ck_reversed[16]; + felica_reverse_copy_block(ck, ck_reversed); + felica_reverse_copy_block(ck + 8, ck_reversed + 8); + + uint8_t rc_reversed[16]; + felica_reverse_copy_block(rc, rc_reversed); + felica_reverse_copy_block(rc + 8, rc_reversed + 8); + + mbedtls_des3_set2key_enc(ctx, ck_reversed); + mbedtls_des3_crypt_cbc(ctx, MBEDTLS_DES_ENCRYPT, FELICA_DATA_BLOCK_SIZE, iv, rc_reversed, out); +} + +static bool felica_calculate_mac( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* first_block, + const uint8_t* data, + const size_t length, + uint8_t* mac) { + furi_check((length % 8) == 0); + + uint8_t reverse_data[8]; + uint8_t iv[8]; + uint8_t out[8]; + mbedtls_des3_set2key_enc(ctx, session_key); + + felica_reverse_copy_block(rc, iv); + felica_reverse_copy_block(first_block, reverse_data); + uint8_t i = 0; + bool error = false; + do { + if(mbedtls_des3_crypt_cbc(ctx, MBEDTLS_DES_ENCRYPT, 8, iv, reverse_data, out) == 0) { + memcpy(iv, out, sizeof(iv)); + felica_reverse_copy_block(data + i, reverse_data); + i += 8; + } else { + error = true; + break; + } + } while(i <= length); + + if(!error) { + felica_reverse_copy_block(out, mac); + } + return !error; +} + +static void felica_prepare_first_block( + FelicaMACType operation_type, + const uint8_t* blocks, + const uint8_t block_count, + uint8_t* out) { + furi_check(blocks); + furi_check(out); + if(operation_type == FelicaMACTypeRead) { + memset(out, 0xFF, 8); + for(uint8_t i = 0, j = 0; i < block_count; i++, j += 2) { + out[j] = blocks[i]; + out[j + 1] = 0; + } + } else { + furi_check(block_count == 4); + memset(out, 0, 8); + out[0] = blocks[0]; + out[1] = blocks[1]; + out[2] = blocks[2]; + out[4] = blocks[3]; + out[6] = FELICA_BLOCK_INDEX_MAC_A; + } +} + +bool felica_check_mac( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* blocks, + const uint8_t block_count, + uint8_t* data) { + furi_check(ctx); + furi_check(session_key); + furi_check(rc); + furi_check(blocks); + furi_check(data); + + uint8_t first_block[8]; + uint8_t mac[8]; + felica_prepare_first_block(FelicaMACTypeRead, blocks, block_count, first_block); + + uint8_t data_size_without_mac = FELICA_DATA_BLOCK_SIZE * (block_count - 1); + felica_calculate_mac(ctx, session_key, rc, first_block, data, data_size_without_mac, mac); + + uint8_t* mac_ptr = data + data_size_without_mac; + return !memcmp(mac, mac_ptr, 8); +} + +void felica_calculate_mac_write( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* wcnt, + const uint8_t* data, + uint8_t* mac) { + furi_check(ctx); + furi_check(session_key); + furi_check(rc); + furi_check(wcnt); + furi_check(data); + furi_check(mac); + + const uint8_t WCNT_length = 3; + uint8_t block_data[WCNT_length + 1]; + uint8_t first_block[8]; + + memcpy(block_data, wcnt, WCNT_length); + block_data[3] = FELICA_BLOCK_INDEX_STATE; + felica_prepare_first_block(FelicaMACTypeWrite, block_data, WCNT_length + 1, first_block); + + uint8_t session_swapped[FELICA_DATA_BLOCK_SIZE]; + memcpy(session_swapped, session_key + 8, 8); + memcpy(session_swapped + 8, session_key, 8); + felica_calculate_mac(ctx, session_swapped, rc, first_block, data, FELICA_DATA_BLOCK_SIZE, mac); +} \ No newline at end of file diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h index e1820d4dc5..d032943d35 100644 --- a/lib/nfc/protocols/felica/felica.h +++ b/lib/nfc/protocols/felica/felica.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -9,6 +10,23 @@ extern "C" { #define FELICA_IDM_SIZE (8U) #define FELICA_PMM_SIZE (8U) +#define FELICA_DATA_BLOCK_SIZE (16U) + +#define FELICA_BLOCKS_TOTAL_COUNT (27U) +#define FELICA_BLOCK_INDEX_REG (0x0EU) +#define FELICA_BLOCK_INDEX_RC (0x80U) +#define FELICA_BLOCK_INDEX_MAC (0x81U) +#define FELICA_BLOCK_INDEX_ID (0x82U) +#define FELICA_BLOCK_INDEX_D_ID (0x83U) +#define FELICA_BLOCK_INDEX_SER_C (0x84U) +#define FELICA_BLOCK_INDEX_SYS_C (0x85U) +#define FELICA_BLOCK_INDEX_CKV (0x86U) +#define FELICA_BLOCK_INDEX_CK (0x87U) +#define FELICA_BLOCK_INDEX_MC (0x88U) +#define FELICA_BLOCK_INDEX_WCNT (0x90U) +#define FELICA_BLOCK_INDEX_MAC_A (0x91U) +#define FELICA_BLOCK_INDEX_STATE (0x92U) +#define FELICA_BLOCK_INDEX_CRC_CHECK (0xA0U) #define FELICA_GUARD_TIME_US (20000U) #define FELICA_FDT_POLL_FC (10000U) @@ -23,6 +41,7 @@ extern "C" { #define FELICA_TIME_SLOT_8 (0x07U) #define FELICA_TIME_SLOT_16 (0x0FU) +/** @brief Type of possible Felica errors */ typedef enum { FelicaErrorNone, FelicaErrorNotPresent, @@ -35,17 +54,78 @@ typedef enum { FelicaErrorTimeout, } FelicaError; +/** @brief Separate type for card key block. Used in authentication process */ +typedef struct { + uint8_t data[FELICA_DATA_BLOCK_SIZE]; +} FelicaCardKey; + +/** @brief In Felica there two types of auth. Internal is the first one, after + * which external became possible. Here are two flags representing which one + * was passed */ +typedef struct { + bool internal : 1; + bool external : 1; +} FelicaAuthenticationStatus; + +/** @brief Struct which controls the process of authentication and can be passed as + * a parameter to the application level. In order to force user to fill card key block data. */ +typedef struct { + bool skip_auth; /**< By default it is true, so auth is skipped. By setting this to false several auth steps will be performed in order to pass auth*/ + FelicaCardKey + card_key; /**< User must fill this field with known card key in order to pass auth*/ + FelicaAuthenticationStatus auth_status; /**< Authentication status*/ +} FelicaAuthenticationContext; + +/** @brief Felica ID block */ typedef struct { uint8_t data[FELICA_IDM_SIZE]; } FelicaIDm; +/** @brief Felica PMm block */ typedef struct { uint8_t data[FELICA_PMM_SIZE]; } FelicaPMm; +/** @brief Felica block with status flags indicating last operation with it. + * See Felica manual for more details on status codes. */ +typedef struct { + uint8_t SF1; /**< Status flag 1, equals to 0 when success*/ + uint8_t SF2; /**< Status flag 2, equals to 0 when success*/ + uint8_t data[FELICA_DATA_BLOCK_SIZE]; /**< Block data */ +} FelicaBlock; + +/** @brief Felica filesystem structure */ +typedef struct { + FelicaBlock spad[14]; + FelicaBlock reg; + FelicaBlock rc; + FelicaBlock mac; + FelicaBlock id; + FelicaBlock d_id; + FelicaBlock ser_c; + FelicaBlock sys_c; + FelicaBlock ckv; + FelicaBlock ck; + FelicaBlock mc; + FelicaBlock wcnt; + FelicaBlock mac_a; + FelicaBlock state; + FelicaBlock crc_check; +} FelicaFileSystem; + +/** @brief Union which represents filesystem in junction with plain data dump */ +typedef union { + FelicaFileSystem fs; + uint8_t dump[sizeof(FelicaFileSystem)]; +} FelicaFSUnion; + +/** @brief Structure used to store Felica data and additional values about reading */ typedef struct { FelicaIDm idm; FelicaPMm pmm; + uint8_t blocks_total; + uint8_t blocks_read; + FelicaFSUnion data; } FelicaData; extern const NfcDeviceBase nfc_device_felica; @@ -74,6 +154,27 @@ bool felica_set_uid(FelicaData* data, const uint8_t* uid, size_t uid_len); FelicaData* felica_get_base_data(const FelicaData* data); +void felica_calculate_session_key( + mbedtls_des3_context* ctx, + const uint8_t* ck, + const uint8_t* rc, + uint8_t* out); + +bool felica_check_mac( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* blocks, + const uint8_t block_count, + uint8_t* data); + +void felica_calculate_mac_write( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* wcnt, + const uint8_t* data, + uint8_t* mac); #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c index 23b1604e19..720daf2384 100644 --- a/lib/nfc/protocols/felica/felica_poller.c +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -3,6 +3,11 @@ #include #include +#include + +#define TAG "FelicaPoller" + +typedef NfcCommand (*FelicaPollerReadHandler)(FelicaPoller* instance); const FelicaData* felica_poller_get_data(FelicaPoller* instance) { furi_assert(instance); @@ -23,6 +28,8 @@ static FelicaPoller* felica_poller_alloc(Nfc* nfc) { nfc_set_guard_time_us(instance->nfc, FELICA_GUARD_TIME_US); nfc_set_fdt_poll_fc(instance->nfc, FELICA_FDT_POLL_FC); nfc_set_fdt_poll_poll_us(instance->nfc, FELICA_POLL_POLL_MIN_US); + + mbedtls_des3_init(&instance->auth.des_context); instance->data = felica_alloc(); instance->felica_event.data = &instance->felica_event_data; @@ -40,6 +47,7 @@ static void felica_poller_free(FelicaPoller* instance) { furi_assert(instance->rx_buffer); furi_assert(instance->data); + mbedtls_des3_free(&instance->auth.des_context); bit_buffer_free(instance->tx_buffer); bit_buffer_free(instance->rx_buffer); felica_free(instance->data); @@ -55,6 +63,212 @@ static void instance->context = context; } +NfcCommand felica_poller_state_handler_idle(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Idle"); + felica_reset(instance->data); + instance->state = FelicaPollerStateActivated; + + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_activate(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Activate"); + + NfcCommand command = NfcCommandContinue; + + FelicaError error = felica_poller_activate(instance, instance->data); + if(error == FelicaErrorNone) { + furi_hal_random_fill_buf(instance->data->data.fs.rc.data, FELICA_DATA_BLOCK_SIZE); + + instance->felica_event.type = FelicaPollerEventTypeRequestAuthContext; + instance->felica_event_data.auth_context = &instance->auth.context; + + instance->callback(instance->general_event, instance->context); + + bool skip_auth = instance->auth.context.skip_auth; + instance->state = skip_auth ? FelicaPollerStateReadBlocks : + FelicaPollerStateAuthenticateInternal; + } else if(error != FelicaErrorTimeout) { + instance->felica_event.type = FelicaPollerEventTypeError; + instance->felica_event_data.error = error; + instance->state = FelicaPollerStateReadFailed; + } + return command; +} + +NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Auth Internal"); + + felica_calculate_session_key( + &instance->auth.des_context, + instance->auth.context.card_key.data, + instance->data->data.fs.rc.data, + instance->auth.session_key.data); + + instance->state = FelicaPollerStateReadBlocks; + + uint8_t blocks[3] = {FELICA_BLOCK_INDEX_RC, 0, 0}; + FelicaPollerWriteCommandResponse* tx_resp; + do { + FelicaError error = felica_poller_write_blocks( + instance, 1, blocks, instance->data->data.fs.rc.data, &tx_resp); + if((error != FelicaErrorNone) || (tx_resp->SF1 != 0) || (tx_resp->SF2 != 0)) break; + + blocks[0] = FELICA_BLOCK_INDEX_ID; + blocks[1] = FELICA_BLOCK_INDEX_WCNT; + blocks[2] = FELICA_BLOCK_INDEX_MAC_A; + FelicaPollerReadCommandResponse* rx_resp; + error = felica_poller_read_blocks(instance, sizeof(blocks), blocks, &rx_resp); + if(error != FelicaErrorNone || rx_resp->SF1 != 0 || rx_resp->SF2 != 0) break; + + if(felica_check_mac( + &instance->auth.des_context, + instance->auth.session_key.data, + instance->data->data.fs.rc.data, + blocks, + rx_resp->block_count, + rx_resp->data)) { + instance->auth.context.auth_status.internal = true; + instance->data->data.fs.wcnt.SF1 = 0; + instance->data->data.fs.wcnt.SF2 = 0; + memcpy( + instance->data->data.fs.wcnt.data, + rx_resp->data + FELICA_DATA_BLOCK_SIZE, + FELICA_DATA_BLOCK_SIZE); + instance->state = FelicaPollerStateAuthenticateExternal; + } + } while(false); + + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_auth_external(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Auth External"); + instance->state = FelicaPollerStateReadBlocks; + uint8_t blocks[2]; + + instance->data->data.fs.state.data[0] = 1; + FelicaAuthentication* auth = &instance->auth; + felica_calculate_mac_write( + &auth->des_context, + auth->session_key.data, + instance->data->data.fs.rc.data, + instance->data->data.fs.wcnt.data, + instance->data->data.fs.state.data, + instance->data->data.fs.mac_a.data); + + memcpy(instance->data->data.fs.mac_a.data + 8, instance->data->data.fs.wcnt.data, 3); //-V1086 + + uint8_t tx_data[FELICA_DATA_BLOCK_SIZE * 2]; + memcpy(tx_data, instance->data->data.fs.state.data, FELICA_DATA_BLOCK_SIZE); + memcpy( + tx_data + FELICA_DATA_BLOCK_SIZE, + instance->data->data.fs.mac_a.data, + FELICA_DATA_BLOCK_SIZE); + + do { + blocks[0] = FELICA_BLOCK_INDEX_STATE; + blocks[1] = FELICA_BLOCK_INDEX_MAC_A; + FelicaPollerWriteCommandResponse* tx_resp; + FelicaError error = felica_poller_write_blocks(instance, 2, blocks, tx_data, &tx_resp); + if(error != FelicaErrorNone || tx_resp->SF1 != 0 || tx_resp->SF2 != 0) break; + + FelicaPollerReadCommandResponse* rx_resp; + error = felica_poller_read_blocks(instance, 1, blocks, &rx_resp); + if(error != FelicaErrorNone || rx_resp->SF1 != 0 || rx_resp->SF2 != 0) break; + + instance->data->data.fs.state.SF1 = 0; + instance->data->data.fs.state.SF2 = 0; + memcpy(instance->data->data.fs.state.data, rx_resp->data, FELICA_DATA_BLOCK_SIZE); + instance->auth.context.auth_status.external = instance->data->data.fs.state.data[0]; + } while(false); + instance->state = FelicaPollerStateReadBlocks; + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_read_blocks(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Read Blocks"); + + uint8_t block_count = 1; + uint8_t block_list[4] = {0, 0, 0, 0}; + block_list[0] = instance->block_index; + + instance->block_index++; + if(instance->block_index == FELICA_BLOCK_INDEX_REG + 1) { + instance->block_index = FELICA_BLOCK_INDEX_RC; + } else if(instance->block_index == FELICA_BLOCK_INDEX_MC + 1) { + instance->block_index = FELICA_BLOCK_INDEX_WCNT; + } else if(instance->block_index == FELICA_BLOCK_INDEX_STATE + 1) { + instance->block_index = FELICA_BLOCK_INDEX_CRC_CHECK; + } + + FelicaPollerReadCommandResponse* response; + FelicaError error = felica_poller_read_blocks(instance, block_count, block_list, &response); + if(error == FelicaErrorNone) { + block_count = (response->SF1 == 0) ? response->block_count : block_count; + uint8_t* data_ptr = + instance->data->data.dump + instance->data->blocks_total * sizeof(FelicaBlock); + + *data_ptr++ = response->SF1; + *data_ptr++ = response->SF2; + + if(response->SF1 == 0) { + uint8_t* response_data_ptr = response->data; + instance->data->blocks_read++; + memcpy(data_ptr, response_data_ptr, FELICA_DATA_BLOCK_SIZE); + } else { + memset(data_ptr, 0, FELICA_DATA_BLOCK_SIZE); + } + instance->data->blocks_total++; + + if(instance->data->blocks_total == FELICA_BLOCKS_TOTAL_COUNT) { + instance->state = FelicaPollerStateReadSuccess; + } + } else { + instance->felica_event.type = FelicaPollerEventTypeError; + instance->felica_event_data.error = error; + instance->state = FelicaPollerStateReadFailed; + } + + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_read_success(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Read Success"); + + if(!instance->auth.context.auth_status.internal || + !instance->auth.context.auth_status.external) { + instance->data->blocks_read--; + instance->felica_event.type = FelicaPollerEventTypeIncomplete; + } else { + memcpy( + instance->data->data.fs.ck.data, + instance->auth.context.card_key.data, + FELICA_DATA_BLOCK_SIZE); + instance->felica_event.type = FelicaPollerEventTypeReady; + } + + instance->felica_event_data.error = FelicaErrorNone; + return instance->callback(instance->general_event, instance->context); +} + +NfcCommand felica_poller_state_handler_read_failed(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Read Fail"); + instance->callback(instance->general_event, instance->context); + + return NfcCommandStop; +} + +static const FelicaPollerReadHandler felica_poller_handler[FelicaPollerStateNum] = { + [FelicaPollerStateIdle] = felica_poller_state_handler_idle, + [FelicaPollerStateActivated] = felica_poller_state_handler_activate, + [FelicaPollerStateAuthenticateInternal] = felica_poller_state_handler_auth_internal, + [FelicaPollerStateAuthenticateExternal] = felica_poller_state_handler_auth_external, + [FelicaPollerStateReadBlocks] = felica_poller_state_handler_read_blocks, + [FelicaPollerStateReadSuccess] = felica_poller_state_handler_read_success, + [FelicaPollerStateReadFailed] = felica_poller_state_handler_read_failed, +}; + static NfcCommand felica_poller_run(NfcGenericEvent event, void* context) { furi_assert(context); furi_assert(event.protocol == NfcProtocolInvalid); @@ -65,24 +279,7 @@ static NfcCommand felica_poller_run(NfcGenericEvent event, void* context) { NfcCommand command = NfcCommandContinue; if(nfc_event->type == NfcEventTypePollerReady) { - if(instance->state != FelicaPollerStateActivated) { - FelicaError error = felica_poller_activate(instance, instance->data); - if(error == FelicaErrorNone) { - instance->felica_event.type = FelicaPollerEventTypeReady; - instance->felica_event_data.error = error; - command = instance->callback(instance->general_event, instance->context); - } else { - instance->felica_event.type = FelicaPollerEventTypeError; - instance->felica_event_data.error = error; - command = instance->callback(instance->general_event, instance->context); - // Add delay to switch context - furi_delay_ms(100); - } - } else { - instance->felica_event.type = FelicaPollerEventTypeReady; - instance->felica_event_data.error = FelicaErrorNone; - command = instance->callback(instance->general_event, instance->context); - } + command = felica_poller_handler[instance->state](instance); } return command; diff --git a/lib/nfc/protocols/felica/felica_poller.h b/lib/nfc/protocols/felica/felica_poller.h index b0e6778a0f..d4366e7672 100644 --- a/lib/nfc/protocols/felica/felica_poller.h +++ b/lib/nfc/protocols/felica/felica_poller.h @@ -19,14 +19,33 @@ typedef struct FelicaPoller FelicaPoller; */ typedef enum { FelicaPollerEventTypeError, /**< An error occured during activation procedure. */ - FelicaPollerEventTypeReady, /**< The card was activated by the poller. */ + FelicaPollerEventTypeReady, /**< The card was activated and fully read by the poller. */ + FelicaPollerEventTypeIncomplete, /**< The card was activated and partly read by the poller. */ + FelicaPollerEventTypeRequestAuthContext, /**< Authentication context was requested by poller. */ } FelicaPollerEventType; +/** + * @brief Stucture for holding Felica session key which is calculated from rc and ck. +*/ +typedef struct { + uint8_t data[FELICA_DATA_BLOCK_SIZE]; +} FelicaSessionKey; + +/** + * @brief Structure used to hold authentication related fields. +*/ +typedef struct { + mbedtls_des3_context des_context; /**< Context for mbedtls des functions. */ + FelicaSessionKey session_key; /**< Calculated session key. */ + FelicaAuthenticationContext context; /**< Public auth context provided to upper levels. */ +} FelicaAuthentication; + /** * @brief Felica poller event data. */ typedef union { FelicaError error; /**< Error code indicating card activation fail reason. */ + FelicaAuthenticationContext* auth_context; /**< Authentication context to be filled by user. */ } FelicaPollerEventData; /** diff --git a/lib/nfc/protocols/felica/felica_poller_i.c b/lib/nfc/protocols/felica/felica_poller_i.c index bfbf150ef9..f7726be32c 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.c +++ b/lib/nfc/protocols/felica/felica_poller_i.c @@ -3,6 +3,11 @@ #include #define TAG "FelicaPoller" +#define FELICA_CMD_READ_WITHOUT_ENCRYPTION (0x06U) +#define FELICA_CMD_WRITE_WITHOUT_ENCRYPTION (0x08U) + +#define FELICA_SERVICE_RW_ACCESS (0x0009U) +#define FELICA_SERVICE_RO_ACCESS (0x000BU) static FelicaError felica_poller_process_error(NfcError error) { switch(error) { @@ -15,8 +20,8 @@ static FelicaError felica_poller_process_error(NfcError error) { } } -static FelicaError felica_poller_frame_exchange( - FelicaPoller* instance, +FelicaError felica_poller_frame_exchange( + const FelicaPoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt) { @@ -93,10 +98,104 @@ FelicaError felica_poller_polling( return error; } -FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data) { +static void felica_poller_prepare_tx_buffer( + const FelicaPoller* instance, + const uint8_t command, + const uint16_t service_code, + const uint8_t block_count, + const uint8_t* const blocks, + const uint8_t data_block_count, + const uint8_t* data) { + FelicaCommandHeader cmd = { + .code = command, + .idm = instance->data->idm, + .service_num = 1, + .service_code = service_code, + .block_count = block_count, + }; + + FelicaBlockListElement block_list[4] = {{0}, {0}, {0}, {0}}; + for(uint8_t i = 0; i < block_count; i++) { + block_list[i].length = 1; + block_list[i].block_number = blocks[i]; + } + + uint8_t block_list_count = block_count; + uint8_t block_list_size = block_list_count * sizeof(FelicaBlockListElement); + uint8_t total_size = sizeof(FelicaCommandHeader) + 1 + block_list_size + + data_block_count * FELICA_DATA_BLOCK_SIZE; + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, total_size); + bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&cmd, sizeof(FelicaCommandHeader)); + bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&block_list, block_list_size); + + if(data_block_count != 0) { + bit_buffer_append_bytes( + instance->tx_buffer, data, data_block_count * FELICA_DATA_BLOCK_SIZE); + } +} + +FelicaError felica_poller_read_blocks( + FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + FelicaPollerReadCommandResponse** const response_ptr) { furi_assert(instance); + furi_assert(block_count <= 4); + furi_assert(block_numbers); + furi_assert(response_ptr); + + felica_poller_prepare_tx_buffer( + instance, + FELICA_CMD_READ_WITHOUT_ENCRYPTION, + FELICA_SERVICE_RO_ACCESS, + block_count, + block_numbers, + 0, + NULL); + bit_buffer_reset(instance->rx_buffer); + + FelicaError error = felica_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT); + if(error == FelicaErrorNone) { + *response_ptr = (FelicaPollerReadCommandResponse*)bit_buffer_get_data(instance->rx_buffer); + } + return error; +} + +FelicaError felica_poller_write_blocks( + const FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + const uint8_t* data, + FelicaPollerWriteCommandResponse** const response_ptr) { + furi_assert(instance); + furi_assert(block_count <= 2); + furi_assert(block_numbers); + furi_assert(data); + furi_assert(response_ptr); + + felica_poller_prepare_tx_buffer( + instance, + FELICA_CMD_WRITE_WITHOUT_ENCRYPTION, + FELICA_SERVICE_RW_ACCESS, + block_count, + block_numbers, + block_count, + data); + bit_buffer_reset(instance->rx_buffer); + + FelicaError error = felica_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT); + if(error == FelicaErrorNone) { + *response_ptr = + (FelicaPollerWriteCommandResponse*)bit_buffer_get_data(instance->rx_buffer); + } + return error; +} - felica_reset(data); +FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data) { + furi_assert(instance); FelicaError ret; diff --git a/lib/nfc/protocols/felica/felica_poller_i.h b/lib/nfc/protocols/felica/felica_poller_i.h index 3bd4d91f9f..f7df4c8450 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.h +++ b/lib/nfc/protocols/felica/felica_poller_i.h @@ -1,7 +1,6 @@ #pragma once #include "felica_poller.h" - #include #ifdef __cplusplus @@ -18,11 +17,20 @@ extern "C" { typedef enum { FelicaPollerStateIdle, FelicaPollerStateActivated, + FelicaPollerStateAuthenticateInternal, + FelicaPollerStateAuthenticateExternal, + FelicaPollerStateReadBlocks, + FelicaPollerStateReadSuccess, + FelicaPollerStateReadFailed, + + FelicaPollerStateNum } FelicaPollerState; struct FelicaPoller { Nfc* nfc; FelicaPollerState state; + FelicaAuthentication auth; + FelicaData* data; BitBuffer* tx_buffer; BitBuffer* rx_buffer; @@ -31,6 +39,7 @@ struct FelicaPoller { FelicaPollerEvent felica_event; FelicaPollerEventData felica_event_data; NfcGenericCallback callback; + uint8_t block_index; void* context; }; @@ -46,13 +55,106 @@ typedef struct { uint8_t request_data[2]; } FelicaPollerPollingResponse; +typedef struct { + uint8_t service_code : 4; + uint8_t access_mode : 3; + uint8_t length : 1; + uint8_t block_number; +} FelicaBlockListElement; + +#pragma pack(push, 1) +typedef struct { + uint8_t code; + FelicaIDm idm; + uint8_t service_num; + uint16_t service_code; + uint8_t block_count; +} FelicaCommandHeader; +#pragma pack(pop) + +typedef struct { + uint8_t length; + uint8_t response_code; + FelicaIDm idm; + uint8_t SF1; + uint8_t SF2; + uint8_t block_count; + uint8_t data[]; +} FelicaPollerReadCommandResponse; + +typedef struct { + uint8_t length; + uint8_t response_code; + FelicaIDm idm; + uint8_t SF1; + uint8_t SF2; +} FelicaPollerWriteCommandResponse; + const FelicaData* felica_poller_get_data(FelicaPoller* instance); +/** + * @brief Performs felica polling operation as part of the activation process + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] cmd Pointer to polling command structure + * @param[out] resp Pointer to the response structure + * @return FelicaErrorNone on success, an error code on failure +*/ FelicaError felica_poller_polling( FelicaPoller* instance, const FelicaPollerPollingCommand* cmd, FelicaPollerPollingResponse* resp); +/** + * @brief Performs felica read operation for blocks provided as parameters + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_count Amount of blocks involved in reading procedure + * @param[in] block_numbers Array with block indexes according to felica docs + * @param[out] response_ptr Pointer to the response structure + * @return FelicaErrorNone on success, an error code on failure. +*/ +FelicaError felica_poller_read_blocks( + FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + FelicaPollerReadCommandResponse** const response_ptr); + +/** + * @brief Performs felica write operation with data provided as parameters + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_count Amount of blocks involved in writing procedure + * @param[in] block_numbers Array with block indexes according to felica docs + * @param[in] data Data of blocks provided in block_numbers + * @param[out] response_ptr Pointer to the response structure + * @return FelicaErrorNone on success, an error code on failure. +*/ +FelicaError felica_poller_write_blocks( + const FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + const uint8_t* data, + FelicaPollerWriteCommandResponse** const response_ptr); + +/** + * @brief Perform frame exchange procedure. + * + * Prepares data for sending by adding crc, after that performs + * low level calls to send package data to the card + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer with data to be transmitted + * @param[out] rx_buffer pointer to the buffer with received data from card + * @param[in] fwt timeout window + * @return FelicaErrorNone on success, an error code on failure. + */ +FelicaError felica_poller_frame_exchange( + const FelicaPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + #ifdef __cplusplus } #endif diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 7944bc58fa..6e597e7d05 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.6,, +Version,+,60.7,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 8fd9f4c9b4..09b8b9ee82 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.6,, +Version,+,60.7,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -133,6 +133,8 @@ Header,+,lib/nfc/nfc_device.h,, Header,+,lib/nfc/nfc_listener.h,, Header,+,lib/nfc/nfc_poller.h,, Header,+,lib/nfc/nfc_scanner.h,, +Header,+,lib/nfc/protocols/felica/felica.h,, +Header,+,lib/nfc/protocols/felica/felica_poller.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h,, @@ -987,6 +989,22 @@ Function,-,fdim,double,"double, double" Function,-,fdimf,float,"float, float" Function,-,fdiml,long double,"long double, long double" Function,-,fdopen,FILE*,"int, const char*" +Function,+,felica_alloc,FelicaData*, +Function,+,felica_calculate_mac_write,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t*, uint8_t*" +Function,+,felica_calculate_session_key,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, uint8_t*" +Function,+,felica_check_mac,_Bool,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*" +Function,+,felica_copy,void,"FelicaData*, const FelicaData*" +Function,+,felica_free,void,FelicaData* +Function,+,felica_get_base_data,FelicaData*,const FelicaData* +Function,+,felica_get_device_name,const char*,"const FelicaData*, NfcDeviceNameType" +Function,+,felica_get_uid,const uint8_t*,"const FelicaData*, size_t*" +Function,+,felica_is_equal,_Bool,"const FelicaData*, const FelicaData*" +Function,+,felica_load,_Bool,"FelicaData*, FlipperFormat*, uint32_t" +Function,+,felica_poller_activate,FelicaError,"FelicaPoller*, FelicaData*" +Function,+,felica_reset,void,FelicaData* +Function,+,felica_save,_Bool,"const FelicaData*, FlipperFormat*" +Function,+,felica_set_uid,_Bool,"FelicaData*, const uint8_t*, size_t" +Function,+,felica_verify,_Bool,"FelicaData*, const FuriString*" Function,-,feof,int,FILE* Function,-,feof_unlocked,int,FILE* Function,-,ferror,int,FILE* @@ -3739,6 +3757,7 @@ Variable,+,message_red_255,const NotificationMessage, Variable,+,message_sound_off,const NotificationMessage, Variable,+,message_vibro_off,const NotificationMessage, Variable,+,message_vibro_on,const NotificationMessage, +Variable,-,nfc_device_felica,const NfcDeviceBase, Variable,-,nfc_device_mf_classic,const NfcDeviceBase, Variable,-,nfc_device_mf_desfire,const NfcDeviceBase, Variable,-,nfc_device_mf_ultralight,const NfcDeviceBase,