Merge remote-tracking branch 'origin/release-candidate' into release
@ -1 +1 @@
|
||||
--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/*
|
||||
--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/*
|
||||
|
||||
11
SConstruct
@ -288,13 +288,17 @@ distenv.PhonyTarget(
|
||||
LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]],
|
||||
)
|
||||
|
||||
# PY_LINT_SOURCES contains recursively-built modules' SConscript files + application manifests
|
||||
# PY_LINT_SOURCES contains recursively-built modules' SConscript files
|
||||
# Here we add additional Python files residing in repo root
|
||||
firmware_env.Append(
|
||||
PY_LINT_SOURCES=[
|
||||
# Py code folders
|
||||
"site_scons",
|
||||
"scripts",
|
||||
"applications",
|
||||
"applications_user",
|
||||
"assets",
|
||||
"targets",
|
||||
# Extra files
|
||||
"SConstruct",
|
||||
"firmware.scons",
|
||||
@ -304,7 +308,10 @@ firmware_env.Append(
|
||||
|
||||
|
||||
black_commandline = "@${PYTHON3} -m black ${PY_BLACK_ARGS} ${PY_LINT_SOURCES}"
|
||||
black_base_args = ["--include", '"\\.scons|\\.py|SConscript|SConstruct"']
|
||||
black_base_args = [
|
||||
"--include",
|
||||
'"(\\.scons|\\.py|SConscript|SConstruct|\\.fam)$"',
|
||||
]
|
||||
|
||||
distenv.PhonyTarget(
|
||||
"lint_py",
|
||||
|
||||
@ -13,12 +13,12 @@ struct ISO7816_Command_APDU {
|
||||
//body
|
||||
uint8_t Lc;
|
||||
uint8_t Le;
|
||||
} __attribute__((packed));
|
||||
} FURI_PACKED;
|
||||
|
||||
struct ISO7816_Response_APDU {
|
||||
uint8_t SW1;
|
||||
uint8_t SW2;
|
||||
} __attribute__((packed));
|
||||
} FURI_PACKED;
|
||||
|
||||
void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen);
|
||||
void iso7816_read_command_apdu(
|
||||
|
||||
@ -4,7 +4,7 @@ App(
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="display_test_app",
|
||||
requires=["gui"],
|
||||
fap_libs=["misc"],
|
||||
fap_libs=["u8g2"],
|
||||
stack_size=1 * 1024,
|
||||
order=120,
|
||||
fap_category="Debug",
|
||||
|
||||
@ -455,4 +455,19 @@ NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) {
|
||||
return NfcErrorNone;
|
||||
}
|
||||
|
||||
NfcError nfc_felica_listener_set_sensf_res_data(
|
||||
Nfc* instance,
|
||||
const uint8_t* idm,
|
||||
const uint8_t idm_len,
|
||||
const uint8_t* pmm,
|
||||
const uint8_t pmm_len) {
|
||||
furi_assert(instance);
|
||||
furi_assert(idm);
|
||||
furi_assert(pmm);
|
||||
furi_assert(idm_len == 8);
|
||||
furi_assert(pmm_len == 8);
|
||||
|
||||
return NfcErrorNone;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,27 +1,31 @@
|
||||
#include "flipper.pb.h"
|
||||
#include <core/check.h>
|
||||
#include <core/record.h>
|
||||
#include "pb_decode.h"
|
||||
#include <rpc/rpc.h>
|
||||
#include "rpc/rpc_i.h"
|
||||
#include "storage.pb.h"
|
||||
#include "storage/filesystem_api_defines.h"
|
||||
#include "storage/storage.h"
|
||||
#include <furi.h>
|
||||
#include "../minunit.h"
|
||||
#include <stdint.h>
|
||||
#include <pb.h>
|
||||
#include <pb_encode.h>
|
||||
#include <m-list.h>
|
||||
#include <lib/toolbox/md5_calc.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
#include <cli/cli.h>
|
||||
#include <loader/loader.h>
|
||||
#include <protobuf_version.h>
|
||||
|
||||
#include <FreeRTOS.h>
|
||||
#include <semphr.h>
|
||||
|
||||
#include <rpc/rpc.h>
|
||||
#include <rpc/rpc_i.h>
|
||||
#include <cli/cli.h>
|
||||
#include <storage/storage.h>
|
||||
#include <loader/loader.h>
|
||||
#include <storage/filesystem_api_defines.h>
|
||||
|
||||
#include <lib/toolbox/md5_calc.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
|
||||
#include <m-list.h>
|
||||
#include "../minunit.h"
|
||||
|
||||
#include <protobuf_version.h>
|
||||
#include <pb.h>
|
||||
#include <pb_encode.h>
|
||||
#include <pb_decode.h>
|
||||
#include <storage.pb.h>
|
||||
#include <flipper.pb.h>
|
||||
|
||||
LIST_DEF(MsgList, PB_Main, M_POD_OPLIST)
|
||||
#define M_OPL_MsgList_t() LIST_OPLIST(MsgList)
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ App(
|
||||
entry_point="advanced_plugin1_ep",
|
||||
requires=["example_advanced_plugins"],
|
||||
sources=["plugin1.c"],
|
||||
fal_embedded=True,
|
||||
)
|
||||
|
||||
App(
|
||||
@ -22,4 +23,5 @@ App(
|
||||
entry_point="advanced_plugin2_ep",
|
||||
requires=["example_advanced_plugins"],
|
||||
sources=["plugin2.c"],
|
||||
fal_embedded=True,
|
||||
)
|
||||
|
||||
@ -23,7 +23,10 @@ int32_t example_advanced_plugins_app(void* p) {
|
||||
PLUGIN_APP_ID, PLUGIN_API_VERSION, composite_api_resolver_get(resolver));
|
||||
|
||||
do {
|
||||
if(plugin_manager_load_all(manager, APP_DATA_PATH("plugins")) != PluginManagerErrorNone) {
|
||||
// For built-in .fals (fal_embedded==True), use APP_ASSETS_PATH
|
||||
// Otherwise, use APP_DATA_PATH
|
||||
if(plugin_manager_load_all(manager, APP_ASSETS_PATH("plugins")) !=
|
||||
PluginManagerErrorNone) {
|
||||
FURI_LOG_E(TAG, "Failed to load all libs");
|
||||
break;
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ App(
|
||||
"!plugins",
|
||||
"!nfc_cli.c",
|
||||
],
|
||||
fap_libs=["assets"],
|
||||
fap_libs=["assets", "mbedtls"],
|
||||
fap_icon="icon.png",
|
||||
fap_category="NFC",
|
||||
)
|
||||
@ -74,6 +74,15 @@ App(
|
||||
sources=["plugins/supported_cards/two_cities.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="aime_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="aime_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
sources=["plugins/supported_cards/aime.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="nfc_start",
|
||||
targets=["f7"],
|
||||
|
||||
@ -67,8 +67,14 @@ static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, uint32_t even
|
||||
return false;
|
||||
}
|
||||
|
||||
static void nfc_scene_emulate_on_enter_felica(NfcApp* instance) {
|
||||
const FelicaData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolFelica);
|
||||
instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolFelica, data);
|
||||
nfc_listener_start(instance->listener, NULL, NULL);
|
||||
}
|
||||
|
||||
const NfcProtocolSupportBase nfc_protocol_support_felica = {
|
||||
.features = NfcProtocolFeatureNone,
|
||||
.features = NfcProtocolFeatureEmulateUid,
|
||||
|
||||
.scene_info =
|
||||
{
|
||||
@ -102,7 +108,7 @@ const NfcProtocolSupportBase nfc_protocol_support_felica = {
|
||||
},
|
||||
.scene_emulate =
|
||||
{
|
||||
.on_enter = nfc_protocol_support_common_on_enter_empty,
|
||||
.on_enter = nfc_scene_emulate_on_enter_felica,
|
||||
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||
},
|
||||
};
|
||||
|
||||
@ -12,6 +12,7 @@ enum {
|
||||
SubmenuIndexUnlock = SubmenuIndexCommonMax,
|
||||
SubmenuIndexUnlockByReader,
|
||||
SubmenuIndexUnlockByPassword,
|
||||
SubmenuIndexWrite,
|
||||
};
|
||||
|
||||
static void nfc_scene_info_on_enter_mf_ultralight(NfcApp* instance) {
|
||||
@ -106,6 +107,15 @@ static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instanc
|
||||
SubmenuIndexUnlock,
|
||||
nfc_protocol_support_common_submenu_callback,
|
||||
instance);
|
||||
} else if(
|
||||
data->type == MfUltralightTypeNTAG213 || data->type == MfUltralightTypeNTAG215 ||
|
||||
data->type == MfUltralightTypeNTAG216) {
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Write",
|
||||
SubmenuIndexWrite,
|
||||
nfc_protocol_support_common_submenu_callback,
|
||||
instance);
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,6 +156,9 @@ static bool
|
||||
if(event == SubmenuIndexUnlock) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu);
|
||||
return true;
|
||||
} else if(event == SubmenuIndexWrite) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -29,7 +29,9 @@ static NfcCommand nfc_scene_read_poller_callback_st25tb(NfcGenericEvent event, v
|
||||
NfcApp* instance = context;
|
||||
const St25tbPollerEvent* st25tb_event = event.event_data;
|
||||
|
||||
if(st25tb_event->type == St25tbPollerEventTypeReady) {
|
||||
if(st25tb_event->type == St25tbPollerEventTypeRequestMode) {
|
||||
st25tb_event->data->mode_request.mode = St25tbPollerModeRead;
|
||||
} else if(st25tb_event->type == St25tbPollerEventTypeSuccess) {
|
||||
nfc_device_set_data(
|
||||
instance->nfc_device, NfcProtocolSt25tb, nfc_poller_get_data(instance->poller));
|
||||
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
|
||||
|
||||
@ -400,15 +400,16 @@ bool nfc_load_from_file_select(NfcApp* instance) {
|
||||
browser_options.base_path = NFC_APP_FOLDER;
|
||||
browser_options.hide_dot_files = true;
|
||||
|
||||
// Input events and views are managed by file_browser
|
||||
bool result = dialog_file_browser_show(
|
||||
instance->dialogs, instance->file_path, instance->file_path, &browser_options);
|
||||
bool success = false;
|
||||
do {
|
||||
// Input events and views are managed by file_browser
|
||||
if(!dialog_file_browser_show(
|
||||
instance->dialogs, instance->file_path, instance->file_path, &browser_options))
|
||||
break;
|
||||
success = nfc_load_file(instance, instance->file_path, true);
|
||||
} while(!success);
|
||||
|
||||
if(result) {
|
||||
result = nfc_load_file(instance, instance->file_path, true);
|
||||
}
|
||||
|
||||
return result;
|
||||
return success;
|
||||
}
|
||||
|
||||
void nfc_show_loading_popup(void* context, bool show) {
|
||||
|
||||
169
applications/main/nfc/plugins/supported_cards/aime.c
Normal file
@ -0,0 +1,169 @@
|
||||
#include "nfc_supported_card_plugin.h"
|
||||
|
||||
#include <flipper_application/flipper_application.h>
|
||||
|
||||
#include <nfc/nfc_device.h>
|
||||
#include <nfc/helpers/nfc_util.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
|
||||
#define TAG "Aime"
|
||||
|
||||
static const uint64_t aime_key = 0x574343467632;
|
||||
|
||||
bool aime_verify(Nfc* nfc) {
|
||||
bool verified = false;
|
||||
|
||||
do {
|
||||
const uint8_t verify_sector = 0;
|
||||
uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector);
|
||||
FURI_LOG_D(TAG, "Verifying sector %u", verify_sector);
|
||||
|
||||
MfClassicKey key = {};
|
||||
nfc_util_num2bytes(aime_key, COUNT_OF(key.data), key.data);
|
||||
|
||||
MfClassicAuthContext auth_ctx = {};
|
||||
MfClassicError error =
|
||||
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx);
|
||||
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
|
||||
break;
|
||||
}
|
||||
|
||||
verified = true;
|
||||
} while(false);
|
||||
|
||||
return verified;
|
||||
}
|
||||
|
||||
static bool aime_read(Nfc* nfc, NfcDevice* device) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(device);
|
||||
|
||||
bool is_read = false;
|
||||
|
||||
MfClassicData* data = mf_classic_alloc();
|
||||
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
do {
|
||||
MfClassicType type = MfClassicType1k;
|
||||
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
data->type = type;
|
||||
MfClassicDeviceKeys keys = {};
|
||||
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||
nfc_util_num2bytes(aime_key, sizeof(MfClassicKey), keys.key_a[i].data);
|
||||
FURI_BIT_SET(keys.key_a_mask, i);
|
||||
nfc_util_num2bytes(aime_key, sizeof(MfClassicKey), keys.key_b[i].data);
|
||||
FURI_BIT_SET(keys.key_b_mask, i);
|
||||
}
|
||||
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_W(TAG, "Failed to read data");
|
||||
break;
|
||||
}
|
||||
|
||||
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
is_read = true;
|
||||
} while(false);
|
||||
|
||||
mf_classic_free(data);
|
||||
|
||||
return is_read;
|
||||
}
|
||||
|
||||
static bool aime_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_assert(device);
|
||||
|
||||
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
// verify key
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 0);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, 6);
|
||||
if(key != aime_key) break;
|
||||
|
||||
// Aime Magic is stored at block 1, starts from byte 0, len 4 bytes
|
||||
const uint8_t* aime_magic = &data->block[1].data[0];
|
||||
|
||||
// verify aime magic
|
||||
if(aime_magic[0] != 'S' || aime_magic[1] != 'B' || aime_magic[2] != 'S' ||
|
||||
aime_magic[3] != 'D')
|
||||
break;
|
||||
|
||||
// Aime checksum is stored at block 1, starts from byte 13, len 3 bytes
|
||||
// seems like only old games checks this? e.g., old versions of Chunithm
|
||||
const uint8_t* aime_checksum = &data->block[1].data[13];
|
||||
|
||||
// Aime access code is stored as decimal hex representation in block 2, starts from byte 6, len 10 bytes
|
||||
const uint8_t* aime_accesscode = &data->block[2].data[6];
|
||||
|
||||
char aime_accesscode_str[24 + 1];
|
||||
snprintf(
|
||||
aime_accesscode_str,
|
||||
sizeof(aime_accesscode_str),
|
||||
"%02x%02x %02x%02x %02x%02x %02x%02x %02x%02x",
|
||||
aime_accesscode[0],
|
||||
aime_accesscode[1],
|
||||
aime_accesscode[2],
|
||||
aime_accesscode[3],
|
||||
aime_accesscode[4],
|
||||
aime_accesscode[5],
|
||||
aime_accesscode[6],
|
||||
aime_accesscode[7],
|
||||
aime_accesscode[8],
|
||||
aime_accesscode[9]);
|
||||
|
||||
// validate decimal hex representation
|
||||
bool code_is_hex = true;
|
||||
for(int i = 0; i < 24; i++) {
|
||||
if(aime_accesscode_str[i] == ' ') continue;
|
||||
if(aime_accesscode_str[i] < '0' || aime_accesscode_str[i] > '9') {
|
||||
code_is_hex = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!code_is_hex) break;
|
||||
|
||||
// Note: Aime access code has some other self-check algorithms that are not public.
|
||||
// This parser does not try to verify the number.
|
||||
|
||||
furi_string_printf(
|
||||
parsed_data,
|
||||
"\e#Aime Card\nAccess Code: \n%s\nChecksum: %02X%02X%02X\n",
|
||||
aime_accesscode_str,
|
||||
aime_checksum[0],
|
||||
aime_checksum[1],
|
||||
aime_checksum[2]);
|
||||
|
||||
parsed = true;
|
||||
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/* Actual implementation of app<>plugin interface */
|
||||
static const NfcSupportedCardsPlugin aime_plugin = {
|
||||
.protocol = NfcProtocolMfClassic,
|
||||
.verify = aime_verify,
|
||||
.read = aime_read,
|
||||
.parse = aime_parse,
|
||||
};
|
||||
|
||||
/* Plugin descriptor to comply with basic plugin specification */
|
||||
static const FlipperAppPluginDescriptor aime_plugin_descriptor = {
|
||||
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||
.entry_point = &aime_plugin,
|
||||
};
|
||||
|
||||
/* Plugin entry point - must return a pointer to const descriptor */
|
||||
const FlipperAppPluginDescriptor* aime_plugin_ep() {
|
||||
return &aime_plugin_descriptor;
|
||||
}
|
||||
@ -24,6 +24,10 @@ ADD_SCENE(nfc, field, Field)
|
||||
ADD_SCENE(nfc, retry_confirm, RetryConfirm)
|
||||
ADD_SCENE(nfc, exit_confirm, ExitConfirm)
|
||||
|
||||
ADD_SCENE(nfc, mf_ultralight_write, MfUltralightWrite)
|
||||
ADD_SCENE(nfc, mf_ultralight_write_success, MfUltralightWriteSuccess)
|
||||
ADD_SCENE(nfc, mf_ultralight_write_fail, MfUltralightWriteFail)
|
||||
ADD_SCENE(nfc, mf_ultralight_wrong_card, MfUltralightWrongCard)
|
||||
ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu)
|
||||
ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn)
|
||||
ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput)
|
||||
|
||||
@ -37,6 +37,7 @@ bool nfc_scene_detect_on_event(void* context, SceneManagerEvent event) {
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcCustomEventWorkerExit) {
|
||||
if(instance->protocols_detected_num > 1) {
|
||||
notification_message(instance->notifications, &sequence_single_vibro);
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneSelectProtocol);
|
||||
} else {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneRead);
|
||||
|
||||
@ -45,11 +45,7 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexMfClassicKeys) {
|
||||
if(nfc_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeys);
|
||||
} else {
|
||||
scene_manager_previous_scene(instance->scene_manager);
|
||||
}
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeys);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexMfUltralightUnlock) {
|
||||
mf_ultralight_auth_reset(instance->mf_ul_auth);
|
||||
|
||||
@ -52,11 +52,12 @@ bool nfc_scene_mf_ultralight_unlock_warn_on_event(void* context, SceneManagerEve
|
||||
|
||||
bool consumed = false;
|
||||
|
||||
nfc->protocols_detected[0] = nfc_device_get_protocol(nfc->nfc_device);
|
||||
MfUltralightAuthType type = nfc->mf_ul_auth->type;
|
||||
if((type == MfUltralightAuthTypeReader) || (type == MfUltralightAuthTypeManual)) {
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == DialogExResultRight) {
|
||||
const NfcProtocol mfu_protocol[] = {NfcProtocolMfUltralight};
|
||||
nfc_app_set_detected_protocols(nfc, mfu_protocol, COUNT_OF(mfu_protocol));
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneRead);
|
||||
dolphin_deed(DolphinDeedNfcRead);
|
||||
consumed = true;
|
||||
|
||||
119
applications/main/nfc/scenes/nfc_scene_mf_ultralight_write.c
Normal file
@ -0,0 +1,119 @@
|
||||
#include "../nfc_app_i.h"
|
||||
|
||||
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller.h>
|
||||
|
||||
enum {
|
||||
NfcSceneMfUltralightWriteStateCardSearch,
|
||||
NfcSceneMfUltralightWriteStateCardFound,
|
||||
};
|
||||
|
||||
NfcCommand nfc_scene_mf_ultralight_write_worker_callback(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.event_data);
|
||||
furi_assert(event.protocol == NfcProtocolMfUltralight);
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
NfcApp* instance = context;
|
||||
MfUltralightPollerEvent* mfu_event = event.event_data;
|
||||
|
||||
if(mfu_event->type == MfUltralightPollerEventTypeRequestMode) {
|
||||
mfu_event->data->poller_mode = MfUltralightPollerModeWrite;
|
||||
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected);
|
||||
} else if(mfu_event->type == MfUltralightPollerEventTypeAuthRequest) {
|
||||
mfu_event->data->auth_context.skip_auth = true;
|
||||
} else if(mfu_event->type == MfUltralightPollerEventTypeRequestWriteData) {
|
||||
mfu_event->data->write_data =
|
||||
nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight);
|
||||
} else if(mfu_event->type == MfUltralightPollerEventTypeCardMismatch) {
|
||||
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard);
|
||||
command = NfcCommandStop;
|
||||
} else if(mfu_event->type == MfUltralightPollerEventTypeCardLocked) {
|
||||
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure);
|
||||
command = NfcCommandStop;
|
||||
} else if(mfu_event->type == MfUltralightPollerEventTypeWriteFail) {
|
||||
command = NfcCommandStop;
|
||||
} else if(mfu_event->type == MfUltralightPollerEventTypeWriteSuccess) {
|
||||
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
|
||||
command = NfcCommandStop;
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
static void nfc_scene_mf_ultralight_write_setup_view(NfcApp* instance) {
|
||||
Popup* popup = instance->popup;
|
||||
popup_reset(popup);
|
||||
uint32_t state =
|
||||
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightWrite);
|
||||
|
||||
if(state == NfcSceneMfUltralightWriteStateCardSearch) {
|
||||
popup_set_text(
|
||||
instance->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter);
|
||||
popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
|
||||
} else {
|
||||
popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter);
|
||||
popup_set_icon(popup, 12, 23, &A_Loading_24);
|
||||
}
|
||||
|
||||
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
|
||||
}
|
||||
|
||||
void nfc_scene_mf_ultralight_write_on_enter(void* context) {
|
||||
NfcApp* instance = context;
|
||||
dolphin_deed(DolphinDeedNfcEmulate);
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager,
|
||||
NfcSceneMfUltralightWrite,
|
||||
NfcSceneMfUltralightWriteStateCardSearch);
|
||||
nfc_scene_mf_ultralight_write_setup_view(instance);
|
||||
|
||||
// Setup and start worker
|
||||
FURI_LOG_D("WMFU", "Card searching...");
|
||||
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight);
|
||||
nfc_poller_start(instance->poller, nfc_scene_mf_ultralight_write_worker_callback, instance);
|
||||
|
||||
nfc_blink_emulate_start(instance);
|
||||
}
|
||||
|
||||
bool nfc_scene_mf_ultralight_write_on_event(void* context, SceneManagerEvent event) {
|
||||
NfcApp* instance = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcCustomEventCardDetected) {
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager,
|
||||
NfcSceneMfUltralightWrite,
|
||||
NfcSceneMfUltralightWriteStateCardFound);
|
||||
nfc_scene_mf_ultralight_write_setup_view(instance);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcCustomEventWrongCard) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrongCard);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcCustomEventPollerSuccess) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWriteSuccess);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcCustomEventPollerFailure) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWriteFail);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_mf_ultralight_write_on_exit(void* context) {
|
||||
NfcApp* instance = context;
|
||||
|
||||
nfc_poller_stop(instance->poller);
|
||||
nfc_poller_free(instance->poller);
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager,
|
||||
NfcSceneMfUltralightWrite,
|
||||
NfcSceneMfUltralightWriteStateCardSearch);
|
||||
// Clear view
|
||||
popup_reset(instance->popup);
|
||||
|
||||
nfc_blink_stop(instance);
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
#include "../nfc_app_i.h"
|
||||
|
||||
void nfc_scene_mf_ultralight_write_fail_widget_callback(
|
||||
GuiButtonType result,
|
||||
InputType type,
|
||||
void* context) {
|
||||
NfcApp* instance = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(instance->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_scene_mf_ultralight_write_fail_on_enter(void* context) {
|
||||
NfcApp* instance = context;
|
||||
Widget* widget = instance->widget;
|
||||
|
||||
notification_message(instance->notifications, &sequence_error);
|
||||
|
||||
widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48);
|
||||
widget_add_string_element(
|
||||
widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!");
|
||||
widget_add_string_multiline_element(
|
||||
widget,
|
||||
7,
|
||||
17,
|
||||
AlignLeft,
|
||||
AlignTop,
|
||||
FontSecondary,
|
||||
"Card protected by\npassword, AUTH0\nor lock bits");
|
||||
|
||||
widget_add_button_element(
|
||||
widget,
|
||||
GuiButtonTypeLeft,
|
||||
"Finish",
|
||||
nfc_scene_mf_ultralight_write_fail_widget_callback,
|
||||
instance);
|
||||
|
||||
// Setup and start worker
|
||||
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
|
||||
}
|
||||
|
||||
static bool nfc_scene_mf_ultralight_write_fail_move_to_back_scene(const NfcApp* const instance) {
|
||||
bool was_saved = scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSavedMenu);
|
||||
uint32_t scene_id = was_saved ? NfcSceneSavedMenu : NfcSceneReadMenu;
|
||||
|
||||
return scene_manager_search_and_switch_to_previous_scene(instance->scene_manager, scene_id);
|
||||
}
|
||||
|
||||
bool nfc_scene_mf_ultralight_write_fail_on_event(void* context, SceneManagerEvent event) {
|
||||
NfcApp* instance = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
consumed = nfc_scene_mf_ultralight_write_fail_move_to_back_scene(instance);
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = nfc_scene_mf_ultralight_write_fail_move_to_back_scene(instance);
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_mf_ultralight_write_fail_on_exit(void* context) {
|
||||
NfcApp* instance = context;
|
||||
|
||||
widget_reset(instance->widget);
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
#include "../nfc_app_i.h"
|
||||
|
||||
void nfc_scene_mf_ultralight_write_success_popup_callback(void* context) {
|
||||
NfcApp* instance = context;
|
||||
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit);
|
||||
}
|
||||
|
||||
void nfc_scene_mf_ultralight_write_success_on_enter(void* context) {
|
||||
NfcApp* instance = context;
|
||||
dolphin_deed(DolphinDeedNfcSave);
|
||||
|
||||
notification_message(instance->notifications, &sequence_success);
|
||||
|
||||
Popup* popup = instance->popup;
|
||||
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
|
||||
popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom);
|
||||
popup_set_timeout(popup, 1500);
|
||||
popup_set_context(popup, instance);
|
||||
popup_set_callback(popup, nfc_scene_mf_ultralight_write_success_popup_callback);
|
||||
popup_enable_timeout(popup);
|
||||
|
||||
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
|
||||
}
|
||||
|
||||
bool nfc_scene_mf_ultralight_write_success_on_event(void* context, SceneManagerEvent event) {
|
||||
NfcApp* instance = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcCustomEventViewExit) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
instance->scene_manager, NfcSceneSavedMenu);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_mf_ultralight_write_success_on_exit(void* context) {
|
||||
NfcApp* instance = context;
|
||||
|
||||
// Clear view
|
||||
popup_reset(instance->popup);
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
#include "../nfc_app_i.h"
|
||||
|
||||
void nfc_scene_mf_ultralight_wrong_card_widget_callback(
|
||||
GuiButtonType result,
|
||||
InputType type,
|
||||
void* context) {
|
||||
NfcApp* instance = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(instance->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_scene_mf_ultralight_wrong_card_on_enter(void* context) {
|
||||
NfcApp* instance = context;
|
||||
Widget* widget = instance->widget;
|
||||
|
||||
notification_message(instance->notifications, &sequence_error);
|
||||
|
||||
widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48);
|
||||
widget_add_string_element(
|
||||
widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card");
|
||||
widget_add_string_multiline_element(
|
||||
widget,
|
||||
4,
|
||||
17,
|
||||
AlignLeft,
|
||||
AlignTop,
|
||||
FontSecondary,
|
||||
"Card of the same\ntype should be\n presented");
|
||||
//"Data management\nis only possible\nwith card of same type");
|
||||
widget_add_button_element(
|
||||
widget,
|
||||
GuiButtonTypeLeft,
|
||||
"Retry",
|
||||
nfc_scene_mf_ultralight_wrong_card_widget_callback,
|
||||
instance);
|
||||
|
||||
// Setup and start worker
|
||||
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
|
||||
}
|
||||
|
||||
bool nfc_scene_mf_ultralight_wrong_card_on_event(void* context, SceneManagerEvent event) {
|
||||
NfcApp* instance = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
consumed = scene_manager_previous_scene(instance->scene_manager);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_mf_ultralight_wrong_card_on_exit(void* context) {
|
||||
NfcApp* instance = context;
|
||||
|
||||
widget_reset(instance->widget);
|
||||
}
|
||||
@ -21,7 +21,6 @@ void nfc_scene_select_protocol_on_enter(void* context) {
|
||||
} else {
|
||||
prefix = "Read as";
|
||||
submenu_set_header(submenu, "Multi-protocol card");
|
||||
notification_message(instance->notifications, &sequence_single_vibro);
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < instance->protocols_detected_num; i++) {
|
||||
|
||||
@ -125,7 +125,6 @@ void dict_attack_reset(DictAttack* instance) {
|
||||
instance->view,
|
||||
DictAttackViewModel * model,
|
||||
{
|
||||
model->card_detected = false;
|
||||
model->sectors_total = 0;
|
||||
model->sectors_read = 0;
|
||||
model->current_sector = 0;
|
||||
|
||||
@ -7,7 +7,7 @@ App(
|
||||
icon="A_U2F_14",
|
||||
order=80,
|
||||
resources="resources",
|
||||
fap_libs=["assets"],
|
||||
fap_libs=["assets", "mbedtls"],
|
||||
fap_category="USB",
|
||||
fap_icon="icon.png",
|
||||
)
|
||||
|
||||
@ -1,98 +0,0 @@
|
||||
/*
|
||||
* hmac.c - HMAC
|
||||
*
|
||||
* Copyright (C) 2017 Sergei Glushchenko
|
||||
* Author: Sergei Glushchenko <gl.sergei@gmail.com>
|
||||
*
|
||||
* This file is a part of U2F firmware for STM32
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* As additional permission under GNU GPL version 3 section 7, you may
|
||||
* distribute non-source form of the Program without the copy of the
|
||||
* GNU GPL normally required by section 4, provided you inform the
|
||||
* recipients of GNU GPL by a written offer.
|
||||
*
|
||||
*/
|
||||
#include <stdint.h>
|
||||
|
||||
#include "sha256.h"
|
||||
#include "hmac_sha256.h"
|
||||
|
||||
static void _hmac_sha256_init(const hmac_context* ctx) {
|
||||
hmac_sha256_context* context = (hmac_sha256_context*)ctx;
|
||||
sha256_start(&context->sha_ctx);
|
||||
}
|
||||
|
||||
static void
|
||||
_hmac_sha256_update(const hmac_context* ctx, const uint8_t* message, unsigned message_size) {
|
||||
hmac_sha256_context* context = (hmac_sha256_context*)ctx;
|
||||
sha256_update(&context->sha_ctx, message, message_size);
|
||||
}
|
||||
|
||||
static void _hmac_sha256_finish(const hmac_context* ctx, uint8_t* hash_result) {
|
||||
hmac_sha256_context* context = (hmac_sha256_context*)ctx;
|
||||
sha256_finish(&context->sha_ctx, hash_result);
|
||||
}
|
||||
|
||||
/* Compute an HMAC using K as a key (as in RFC 6979). Note that K is always
|
||||
the same size as the hash result size. */
|
||||
static void hmac_init(const hmac_context* ctx, const uint8_t* K) {
|
||||
uint8_t* pad = ctx->tmp + 2 * ctx->result_size;
|
||||
unsigned i;
|
||||
for(i = 0; i < ctx->result_size; ++i) pad[i] = K[i] ^ 0x36;
|
||||
for(; i < ctx->block_size; ++i) pad[i] = 0x36;
|
||||
|
||||
ctx->init_hash(ctx);
|
||||
ctx->update_hash(ctx, pad, ctx->block_size);
|
||||
}
|
||||
|
||||
static void hmac_update(const hmac_context* ctx, const uint8_t* message, unsigned message_size) {
|
||||
ctx->update_hash(ctx, message, message_size);
|
||||
}
|
||||
|
||||
static void hmac_finish(const hmac_context* ctx, const uint8_t* K, uint8_t* result) {
|
||||
uint8_t* pad = ctx->tmp + 2 * ctx->result_size;
|
||||
unsigned i;
|
||||
for(i = 0; i < ctx->result_size; ++i) pad[i] = K[i] ^ 0x5c;
|
||||
for(; i < ctx->block_size; ++i) pad[i] = 0x5c;
|
||||
|
||||
ctx->finish_hash(ctx, result);
|
||||
|
||||
ctx->init_hash(ctx);
|
||||
ctx->update_hash(ctx, pad, ctx->block_size);
|
||||
ctx->update_hash(ctx, result, ctx->result_size);
|
||||
ctx->finish_hash(ctx, result);
|
||||
}
|
||||
|
||||
void hmac_sha256_init(hmac_sha256_context* ctx, const uint8_t* K) {
|
||||
ctx->hmac_ctx.init_hash = _hmac_sha256_init;
|
||||
ctx->hmac_ctx.update_hash = _hmac_sha256_update;
|
||||
ctx->hmac_ctx.finish_hash = _hmac_sha256_finish;
|
||||
ctx->hmac_ctx.block_size = 64;
|
||||
ctx->hmac_ctx.result_size = 32;
|
||||
ctx->hmac_ctx.tmp = ctx->tmp;
|
||||
hmac_init(&ctx->hmac_ctx, K);
|
||||
}
|
||||
|
||||
void hmac_sha256_update(
|
||||
const hmac_sha256_context* ctx,
|
||||
const uint8_t* message,
|
||||
unsigned message_size) {
|
||||
hmac_update(&ctx->hmac_ctx, message, message_size);
|
||||
}
|
||||
|
||||
void hmac_sha256_finish(const hmac_sha256_context* ctx, const uint8_t* K, uint8_t* hash_result) {
|
||||
hmac_finish(&ctx->hmac_ctx, K, hash_result);
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "sha256.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct hmac_context {
|
||||
void (*init_hash)(const struct hmac_context* context);
|
||||
void (*update_hash)(
|
||||
const struct hmac_context* context,
|
||||
const uint8_t* message,
|
||||
unsigned message_size);
|
||||
void (*finish_hash)(const struct hmac_context* context, uint8_t* hash_result);
|
||||
unsigned block_size; /* Hash function block size in bytes, eg 64 for SHA-256. */
|
||||
unsigned result_size; /* Hash function result size in bytes, eg 32 for SHA-256. */
|
||||
uint8_t* tmp; /* Must point to a buffer of at least (2 * result_size + block_size) bytes. */
|
||||
} hmac_context;
|
||||
|
||||
typedef struct hmac_sha256_context {
|
||||
hmac_context hmac_ctx;
|
||||
sha256_context sha_ctx;
|
||||
uint8_t tmp[32 * 2 + 64];
|
||||
} hmac_sha256_context;
|
||||
|
||||
void hmac_sha256_init(hmac_sha256_context* ctx, const uint8_t* K);
|
||||
|
||||
void hmac_sha256_update(
|
||||
const hmac_sha256_context* ctx,
|
||||
const uint8_t* message,
|
||||
unsigned message_size);
|
||||
|
||||
void hmac_sha256_finish(const hmac_sha256_context* ctx, const uint8_t* K, uint8_t* hash_result);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -1,18 +1,22 @@
|
||||
#include <furi.h>
|
||||
#include "u2f.h"
|
||||
#include "u2f_hid.h"
|
||||
#include "u2f_data.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_random.h>
|
||||
#include <littlefs/lfs_util.h> // for lfs_tobe32
|
||||
|
||||
#include "toolbox/sha256.h"
|
||||
#include "hmac_sha256.h"
|
||||
#include "micro-ecc/uECC.h"
|
||||
#include <mbedtls/sha256.h>
|
||||
#include <mbedtls/md.h>
|
||||
#include <mbedtls/ecdsa.h>
|
||||
#include <mbedtls/error.h>
|
||||
|
||||
#define TAG "U2f"
|
||||
#define WORKER_TAG TAG "Worker"
|
||||
|
||||
#define MCHECK(expr) furi_check((expr) == 0)
|
||||
|
||||
#define U2F_CMD_REGISTER 0x01
|
||||
#define U2F_CMD_AUTHENTICATE 0x02
|
||||
#define U2F_CMD_VERSION 0x03
|
||||
@ -25,16 +29,26 @@ typedef enum {
|
||||
0x08, // "dont-enforce-user-presence-and-sign" - send auth response even if user is missing
|
||||
} U2fAuthMode;
|
||||
|
||||
#define U2F_HASH_SIZE 32
|
||||
#define U2F_NONCE_SIZE 32
|
||||
#define U2F_CHALLENGE_SIZE 32
|
||||
#define U2F_APP_ID_SIZE 32
|
||||
|
||||
#define U2F_EC_KEY_SIZE 32
|
||||
#define U2F_EC_BIGNUM_SIZE 32
|
||||
#define U2F_EC_POINT_SIZE 65
|
||||
|
||||
typedef struct {
|
||||
uint8_t format;
|
||||
uint8_t xy[64];
|
||||
} __attribute__((packed)) U2fPubKey;
|
||||
} FURI_PACKED U2fPubKey;
|
||||
_Static_assert(sizeof(U2fPubKey) == U2F_EC_POINT_SIZE, "U2fPubKey size mismatch");
|
||||
|
||||
typedef struct {
|
||||
uint8_t len;
|
||||
uint8_t hash[32];
|
||||
uint8_t nonce[32];
|
||||
} __attribute__((packed)) U2fKeyHandle;
|
||||
uint8_t hash[U2F_HASH_SIZE];
|
||||
uint8_t nonce[U2F_NONCE_SIZE];
|
||||
} FURI_PACKED U2fKeyHandle;
|
||||
|
||||
typedef struct {
|
||||
uint8_t cla;
|
||||
@ -42,16 +56,16 @@ typedef struct {
|
||||
uint8_t p1;
|
||||
uint8_t p2;
|
||||
uint8_t len[3];
|
||||
uint8_t challenge[32];
|
||||
uint8_t app_id[32];
|
||||
} __attribute__((packed)) U2fRegisterReq;
|
||||
uint8_t challenge[U2F_CHALLENGE_SIZE];
|
||||
uint8_t app_id[U2F_APP_ID_SIZE];
|
||||
} FURI_PACKED U2fRegisterReq;
|
||||
|
||||
typedef struct {
|
||||
uint8_t reserved;
|
||||
U2fPubKey pub_key;
|
||||
U2fKeyHandle key_handle;
|
||||
uint8_t cert[];
|
||||
} __attribute__((packed)) U2fRegisterResp;
|
||||
} FURI_PACKED U2fRegisterResp;
|
||||
|
||||
typedef struct {
|
||||
uint8_t cla;
|
||||
@ -59,16 +73,16 @@ typedef struct {
|
||||
uint8_t p1;
|
||||
uint8_t p2;
|
||||
uint8_t len[3];
|
||||
uint8_t challenge[32];
|
||||
uint8_t app_id[32];
|
||||
uint8_t challenge[U2F_CHALLENGE_SIZE];
|
||||
uint8_t app_id[U2F_APP_ID_SIZE];
|
||||
U2fKeyHandle key_handle;
|
||||
} __attribute__((packed)) U2fAuthReq;
|
||||
} FURI_PACKED U2fAuthReq;
|
||||
|
||||
typedef struct {
|
||||
uint8_t user_present;
|
||||
uint32_t counter;
|
||||
uint8_t signature[];
|
||||
} __attribute__((packed)) U2fAuthResp;
|
||||
} FURI_PACKED U2fAuthResp;
|
||||
|
||||
static const uint8_t ver_str[] = {"U2F_V2"};
|
||||
|
||||
@ -78,19 +92,20 @@ static const uint8_t state_user_missing[] = {0x69, 0x85};
|
||||
static const uint8_t state_wrong_data[] = {0x6A, 0x80};
|
||||
|
||||
struct U2fData {
|
||||
uint8_t device_key[32];
|
||||
uint8_t cert_key[32];
|
||||
uint8_t device_key[U2F_EC_KEY_SIZE];
|
||||
uint8_t cert_key[U2F_EC_KEY_SIZE];
|
||||
uint32_t counter;
|
||||
const struct uECC_Curve_t* p_curve;
|
||||
bool ready;
|
||||
bool user_present;
|
||||
U2fEvtCallback callback;
|
||||
void* context;
|
||||
mbedtls_ecp_group group;
|
||||
};
|
||||
|
||||
static int u2f_uecc_random(uint8_t* dest, unsigned size) {
|
||||
static int u2f_uecc_random_cb(void* context, uint8_t* dest, unsigned size) {
|
||||
UNUSED(context);
|
||||
furi_hal_random_fill_buf(dest, size);
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
U2fData* u2f_alloc() {
|
||||
@ -99,6 +114,7 @@ U2fData* u2f_alloc() {
|
||||
|
||||
void u2f_free(U2fData* U2F) {
|
||||
furi_assert(U2F);
|
||||
mbedtls_ecp_group_free(&U2F->group);
|
||||
free(U2F);
|
||||
}
|
||||
|
||||
@ -129,8 +145,8 @@ bool u2f_init(U2fData* U2F) {
|
||||
}
|
||||
}
|
||||
|
||||
U2F->p_curve = uECC_secp256r1();
|
||||
uECC_set_rng(u2f_uecc_random);
|
||||
mbedtls_ecp_group_init(&U2F->group);
|
||||
mbedtls_ecp_group_load(&U2F->group, MBEDTLS_ECP_DP_SECP256R1);
|
||||
|
||||
U2F->ready = true;
|
||||
return true;
|
||||
@ -171,21 +187,63 @@ static uint8_t u2f_der_encode_signature(uint8_t* der, uint8_t* sig) {
|
||||
der[0] = 0x30;
|
||||
|
||||
uint8_t len = 2;
|
||||
len += u2f_der_encode_int(der + len, sig, 32);
|
||||
len += u2f_der_encode_int(der + len, sig + 32, 32);
|
||||
len += u2f_der_encode_int(der + len, sig, U2F_HASH_SIZE);
|
||||
len += u2f_der_encode_int(der + len, sig + U2F_HASH_SIZE, U2F_HASH_SIZE);
|
||||
|
||||
der[1] = len - 2;
|
||||
return len;
|
||||
}
|
||||
|
||||
static void
|
||||
u2f_ecc_sign(mbedtls_ecp_group* grp, const uint8_t* key, uint8_t* hash, uint8_t* signature) {
|
||||
mbedtls_mpi r, s, d;
|
||||
|
||||
mbedtls_mpi_init(&r);
|
||||
mbedtls_mpi_init(&s);
|
||||
mbedtls_mpi_init(&d);
|
||||
|
||||
MCHECK(mbedtls_mpi_read_binary(&d, key, U2F_EC_KEY_SIZE));
|
||||
MCHECK(mbedtls_ecdsa_sign(grp, &r, &s, &d, hash, U2F_HASH_SIZE, u2f_uecc_random_cb, NULL));
|
||||
MCHECK(mbedtls_mpi_write_binary(&r, signature, U2F_EC_BIGNUM_SIZE));
|
||||
MCHECK(mbedtls_mpi_write_binary(&s, signature + U2F_EC_BIGNUM_SIZE, U2F_EC_BIGNUM_SIZE));
|
||||
|
||||
mbedtls_mpi_free(&r);
|
||||
mbedtls_mpi_free(&s);
|
||||
mbedtls_mpi_free(&d);
|
||||
}
|
||||
|
||||
static void u2f_ecc_compute_public_key(
|
||||
mbedtls_ecp_group* grp,
|
||||
const uint8_t* private_key,
|
||||
U2fPubKey* public_key) {
|
||||
mbedtls_ecp_point Q;
|
||||
mbedtls_mpi d;
|
||||
size_t olen;
|
||||
|
||||
mbedtls_ecp_point_init(&Q);
|
||||
mbedtls_mpi_init(&d);
|
||||
|
||||
MCHECK(mbedtls_mpi_read_binary(&d, private_key, U2F_EC_KEY_SIZE));
|
||||
MCHECK(mbedtls_ecp_mul(grp, &Q, &d, &grp->G, u2f_uecc_random_cb, NULL));
|
||||
MCHECK(mbedtls_ecp_check_privkey(grp, &d));
|
||||
|
||||
MCHECK(mbedtls_ecp_point_write_binary(
|
||||
grp, &Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, (unsigned char*)public_key, sizeof(U2fPubKey)));
|
||||
|
||||
mbedtls_ecp_point_free(&Q);
|
||||
mbedtls_mpi_free(&d);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
|
||||
static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) {
|
||||
U2fRegisterReq* req = (U2fRegisterReq*)buf;
|
||||
U2fRegisterResp* resp = (U2fRegisterResp*)buf;
|
||||
U2fKeyHandle handle;
|
||||
uint8_t private[32];
|
||||
uint8_t private[U2F_EC_KEY_SIZE];
|
||||
U2fPubKey pub_key;
|
||||
uint8_t hash[32];
|
||||
uint8_t signature[64];
|
||||
uint8_t hash[U2F_HASH_SIZE];
|
||||
uint8_t signature[U2F_EC_BIGNUM_SIZE * 2];
|
||||
|
||||
if(u2f_data_check(false) == false) {
|
||||
U2F->ready = false;
|
||||
@ -201,40 +259,54 @@ static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) {
|
||||
}
|
||||
U2F->user_present = false;
|
||||
|
||||
hmac_sha256_context hmac_ctx;
|
||||
sha256_context sha_ctx;
|
||||
handle.len = U2F_HASH_SIZE * 2;
|
||||
|
||||
handle.len = 32 * 2;
|
||||
// Generate random nonce
|
||||
furi_hal_random_fill_buf(handle.nonce, 32);
|
||||
|
||||
// Generate private key
|
||||
hmac_sha256_init(&hmac_ctx, U2F->device_key);
|
||||
hmac_sha256_update(&hmac_ctx, req->app_id, 32);
|
||||
hmac_sha256_update(&hmac_ctx, handle.nonce, 32);
|
||||
hmac_sha256_finish(&hmac_ctx, U2F->device_key, private);
|
||||
{
|
||||
mbedtls_md_context_t hmac_ctx;
|
||||
mbedtls_md_init(&hmac_ctx);
|
||||
MCHECK(mbedtls_md_setup(&hmac_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1));
|
||||
MCHECK(mbedtls_md_hmac_starts(&hmac_ctx, U2F->device_key, sizeof(U2F->device_key)));
|
||||
|
||||
// Generate private key handle
|
||||
hmac_sha256_init(&hmac_ctx, U2F->device_key);
|
||||
hmac_sha256_update(&hmac_ctx, private, 32);
|
||||
hmac_sha256_update(&hmac_ctx, req->app_id, 32);
|
||||
hmac_sha256_finish(&hmac_ctx, U2F->device_key, handle.hash);
|
||||
// Generate private key
|
||||
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id)));
|
||||
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, handle.nonce, sizeof(handle.nonce)));
|
||||
MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, private));
|
||||
|
||||
MCHECK(mbedtls_md_hmac_reset(&hmac_ctx));
|
||||
|
||||
// Generate private key handle
|
||||
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, private, sizeof(private)));
|
||||
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id)));
|
||||
MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, handle.hash));
|
||||
}
|
||||
|
||||
// Generate public key
|
||||
pub_key.format = 0x04; // Uncompressed point
|
||||
uECC_compute_public_key(private, pub_key.xy, U2F->p_curve);
|
||||
u2f_ecc_compute_public_key(&U2F->group, private, &pub_key);
|
||||
|
||||
// Generate signature
|
||||
uint8_t reserved_byte = 0;
|
||||
sha256_start(&sha_ctx);
|
||||
sha256_update(&sha_ctx, &reserved_byte, 1);
|
||||
sha256_update(&sha_ctx, req->app_id, 32);
|
||||
sha256_update(&sha_ctx, req->challenge, 32);
|
||||
sha256_update(&sha_ctx, handle.hash, handle.len);
|
||||
sha256_update(&sha_ctx, (uint8_t*)&pub_key, 65);
|
||||
sha256_finish(&sha_ctx, hash);
|
||||
{
|
||||
uint8_t reserved_byte = 0;
|
||||
|
||||
uECC_sign(U2F->cert_key, hash, 32, signature, U2F->p_curve);
|
||||
mbedtls_sha256_context sha_ctx;
|
||||
|
||||
mbedtls_sha256_init(&sha_ctx);
|
||||
mbedtls_sha256_starts(&sha_ctx, 0);
|
||||
|
||||
mbedtls_sha256_update(&sha_ctx, &reserved_byte, 1);
|
||||
mbedtls_sha256_update(&sha_ctx, req->app_id, sizeof(req->app_id));
|
||||
mbedtls_sha256_update(&sha_ctx, req->challenge, sizeof(req->challenge));
|
||||
mbedtls_sha256_update(&sha_ctx, handle.hash, handle.len);
|
||||
mbedtls_sha256_update(&sha_ctx, (uint8_t*)&pub_key, sizeof(U2fPubKey));
|
||||
|
||||
mbedtls_sha256_finish(&sha_ctx, hash);
|
||||
mbedtls_sha256_free(&sha_ctx);
|
||||
}
|
||||
|
||||
// Sign hash
|
||||
u2f_ecc_sign(&U2F->group, U2F->cert_key, hash, signature);
|
||||
|
||||
// Encode response message
|
||||
resp->reserved = 0x05;
|
||||
@ -250,13 +322,11 @@ static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) {
|
||||
static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
|
||||
U2fAuthReq* req = (U2fAuthReq*)buf;
|
||||
U2fAuthResp* resp = (U2fAuthResp*)buf;
|
||||
uint8_t priv_key[32];
|
||||
uint8_t priv_key[U2F_EC_KEY_SIZE];
|
||||
uint8_t mac_control[32];
|
||||
hmac_sha256_context hmac_ctx;
|
||||
sha256_context sha_ctx;
|
||||
uint8_t flags = 0;
|
||||
uint8_t hash[32];
|
||||
uint8_t signature[64];
|
||||
uint8_t hash[U2F_HASH_SIZE];
|
||||
uint8_t signature[U2F_HASH_SIZE * 2];
|
||||
uint32_t be_u2f_counter;
|
||||
|
||||
if(u2f_data_check(false) == false) {
|
||||
@ -281,26 +351,42 @@ static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
|
||||
be_u2f_counter = lfs_tobe32(U2F->counter + 1);
|
||||
|
||||
// Generate hash
|
||||
sha256_start(&sha_ctx);
|
||||
sha256_update(&sha_ctx, req->app_id, 32);
|
||||
sha256_update(&sha_ctx, &flags, 1);
|
||||
sha256_update(&sha_ctx, (uint8_t*)&(be_u2f_counter), 4);
|
||||
sha256_update(&sha_ctx, req->challenge, 32);
|
||||
sha256_finish(&sha_ctx, hash);
|
||||
{
|
||||
mbedtls_sha256_context sha_ctx;
|
||||
|
||||
// Recover private key
|
||||
hmac_sha256_init(&hmac_ctx, U2F->device_key);
|
||||
hmac_sha256_update(&hmac_ctx, req->app_id, 32);
|
||||
hmac_sha256_update(&hmac_ctx, req->key_handle.nonce, 32);
|
||||
hmac_sha256_finish(&hmac_ctx, U2F->device_key, priv_key);
|
||||
mbedtls_sha256_init(&sha_ctx);
|
||||
mbedtls_sha256_starts(&sha_ctx, 0);
|
||||
|
||||
// Generate and verify private key handle
|
||||
hmac_sha256_init(&hmac_ctx, U2F->device_key);
|
||||
hmac_sha256_update(&hmac_ctx, priv_key, 32);
|
||||
hmac_sha256_update(&hmac_ctx, req->app_id, 32);
|
||||
hmac_sha256_finish(&hmac_ctx, U2F->device_key, mac_control);
|
||||
mbedtls_sha256_update(&sha_ctx, req->app_id, sizeof(req->app_id));
|
||||
mbedtls_sha256_update(&sha_ctx, &flags, 1);
|
||||
mbedtls_sha256_update(&sha_ctx, (uint8_t*)&(be_u2f_counter), sizeof(be_u2f_counter));
|
||||
mbedtls_sha256_update(&sha_ctx, req->challenge, sizeof(req->challenge));
|
||||
|
||||
if(memcmp(req->key_handle.hash, mac_control, 32) != 0) {
|
||||
mbedtls_sha256_finish(&sha_ctx, hash);
|
||||
mbedtls_sha256_free(&sha_ctx);
|
||||
}
|
||||
|
||||
{
|
||||
mbedtls_md_context_t hmac_ctx;
|
||||
mbedtls_md_init(&hmac_ctx);
|
||||
MCHECK(mbedtls_md_setup(&hmac_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1));
|
||||
MCHECK(mbedtls_md_hmac_starts(&hmac_ctx, U2F->device_key, sizeof(U2F->device_key)));
|
||||
|
||||
// Recover private key
|
||||
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id)));
|
||||
MCHECK(mbedtls_md_hmac_update(
|
||||
&hmac_ctx, req->key_handle.nonce, sizeof(req->key_handle.nonce)));
|
||||
MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, priv_key));
|
||||
|
||||
MCHECK(mbedtls_md_hmac_reset(&hmac_ctx));
|
||||
|
||||
// Generate and verify private key handle
|
||||
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, priv_key, sizeof(priv_key)));
|
||||
MCHECK(mbedtls_md_hmac_update(&hmac_ctx, req->app_id, sizeof(req->app_id)));
|
||||
MCHECK(mbedtls_md_hmac_finish(&hmac_ctx, mac_control));
|
||||
}
|
||||
|
||||
if(memcmp(req->key_handle.hash, mac_control, sizeof(mac_control)) != 0) {
|
||||
FURI_LOG_W(TAG, "Wrong handle!");
|
||||
memcpy(&buf[0], state_wrong_data, 2);
|
||||
return 2;
|
||||
@ -311,7 +397,8 @@ static uint16_t u2f_authenticate(U2fData* U2F, uint8_t* buf) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
uECC_sign(priv_key, hash, 32, signature, U2F->p_curve);
|
||||
// Sign hash
|
||||
u2f_ecc_sign(&U2F->group, priv_key, hash, signature);
|
||||
|
||||
resp->user_present = flags;
|
||||
resp->counter = be_u2f_counter;
|
||||
|
||||
@ -37,7 +37,7 @@ typedef struct {
|
||||
uint32_t counter;
|
||||
uint8_t random_salt[24];
|
||||
uint32_t control;
|
||||
} __attribute__((packed)) U2fCounterData;
|
||||
} FURI_PACKED U2fCounterData;
|
||||
|
||||
bool u2f_data_check(bool cert_only) {
|
||||
bool state = false;
|
||||
|
||||
@ -202,7 +202,7 @@ uint16_t canvas_string_width(Canvas* canvas, const char* str) {
|
||||
return u8g2_GetStrWidth(&canvas->fb, str);
|
||||
}
|
||||
|
||||
uint8_t canvas_glyph_width(Canvas* canvas, char symbol) {
|
||||
uint8_t canvas_glyph_width(Canvas* canvas, uint16_t symbol) {
|
||||
furi_assert(canvas);
|
||||
return u8g2_GetGlyphWidth(&canvas->fb, symbol);
|
||||
}
|
||||
|
||||
@ -214,7 +214,7 @@ uint16_t canvas_string_width(Canvas* canvas, const char* str);
|
||||
*
|
||||
* @return width in pixels
|
||||
*/
|
||||
uint8_t canvas_glyph_width(Canvas* canvas, char symbol);
|
||||
uint8_t canvas_glyph_width(Canvas* canvas, uint16_t symbol);
|
||||
|
||||
/** Draw bitmap picture at position defined by x,y.
|
||||
*
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
#include "flipper.pb.h"
|
||||
#include "rpc_i.h"
|
||||
#include "gui.pb.h"
|
||||
#include <gui/gui_i.h>
|
||||
#include <assets_icons.h>
|
||||
|
||||
#include <flipper.pb.h>
|
||||
#include <gui.pb.h>
|
||||
|
||||
#define TAG "RpcGui"
|
||||
|
||||
typedef enum {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
#include "rpc.h"
|
||||
#include "storage/filesystem_api_defines.h"
|
||||
#include <storage/filesystem_api_defines.h>
|
||||
#include <pb.h>
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
#include "flipper.pb.h"
|
||||
#include <core/common_defines.h>
|
||||
#include <core/memmgr.h>
|
||||
#include <core/record.h>
|
||||
#include "pb_decode.h"
|
||||
#include "rpc/rpc.h"
|
||||
#include "rpc_i.h"
|
||||
#include "storage.pb.h"
|
||||
#include "storage/filesystem_api_defines.h"
|
||||
#include "storage/storage.h"
|
||||
#include <stdint.h>
|
||||
#include <rpc/rpc.h>
|
||||
#include <rpc/rpc_i.h>
|
||||
#include <storage/filesystem_api_defines.h>
|
||||
#include <storage/storage.h>
|
||||
#include <lib/toolbox/md5_calc.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
#include <update_util/lfs_backup.h>
|
||||
|
||||
#include <pb_decode.h>
|
||||
#include <storage.pb.h>
|
||||
#include <flipper.pb.h>
|
||||
|
||||
#define TAG "RpcStorage"
|
||||
|
||||
#define MAX_NAME_LENGTH 255
|
||||
|
||||
|
Before Width: | Height: | Size: 2.4 KiB |
BIN
applications/system/hid_app/assets/Alt_17x10.png
Normal file
|
After Width: | Height: | Size: 550 B |
|
Before Width: | Height: | Size: 2.4 KiB |
BIN
applications/system/hid_app/assets/Cmd_17x10.png
Normal file
|
After Width: | Height: | Size: 556 B |
|
Before Width: | Height: | Size: 2.4 KiB |
BIN
applications/system/hid_app/assets/Ctrl_17x10.png
Normal file
|
After Width: | Height: | Size: 552 B |
|
Before Width: | Height: | Size: 2.4 KiB |
BIN
applications/system/hid_app/assets/Del_17x10.png
Normal file
|
After Width: | Height: | Size: 551 B |
|
Before Width: | Height: | Size: 2.4 KiB |
BIN
applications/system/hid_app/assets/Esc_17x10.png
Normal file
|
After Width: | Height: | Size: 550 B |
|
Before Width: | Height: | Size: 2.4 KiB |
BIN
applications/system/hid_app/assets/Tab_17x10.png
Normal file
|
After Width: | Height: | Size: 549 B |
@ -14,8 +14,22 @@ enum HidDebugSubmenuIndex {
|
||||
HidSubmenuIndexMouse,
|
||||
HidSubmenuIndexMouseClicker,
|
||||
HidSubmenuIndexMouseJiggler,
|
||||
HidSubmenuIndexRemovePairing,
|
||||
};
|
||||
|
||||
static void bt_hid_remove_pairing(Bt* bt) {
|
||||
bt_disconnect(bt);
|
||||
|
||||
// Wait 2nd core to update nvm storage
|
||||
furi_delay_ms(200);
|
||||
|
||||
furi_hal_bt_stop_advertising();
|
||||
|
||||
bt_forget_bonded_devices(bt);
|
||||
|
||||
furi_hal_bt_start_advertising();
|
||||
}
|
||||
|
||||
static void hid_submenu_callback(void* context, uint32_t index) {
|
||||
furi_assert(context);
|
||||
Hid* app = context;
|
||||
@ -45,6 +59,8 @@ static void hid_submenu_callback(void* context, uint32_t index) {
|
||||
} else if(index == HidSubmenuIndexMouseJiggler) {
|
||||
app->view_id = HidViewMouseJiggler;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler);
|
||||
} else if(index == HidSubmenuIndexRemovePairing) {
|
||||
bt_hid_remove_pairing(app->bt);
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,6 +159,14 @@ Hid* hid_alloc(HidTransport transport) {
|
||||
HidSubmenuIndexMouseJiggler,
|
||||
hid_submenu_callback,
|
||||
app);
|
||||
if(transport == HidTransportBle) {
|
||||
submenu_add_item(
|
||||
app->device_type_submenu,
|
||||
"Remove Pairing",
|
||||
HidSubmenuIndexRemovePairing,
|
||||
hid_submenu_callback,
|
||||
app);
|
||||
}
|
||||
view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu));
|
||||
|
||||
@ -49,7 +49,7 @@ typedef struct {
|
||||
#define ROW_COUNT 7
|
||||
#define COLUMN_COUNT 12
|
||||
|
||||
// 0 width items are not drawn, but there value is used
|
||||
// 0 width items are not drawn, but their value is used
|
||||
const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = {
|
||||
{
|
||||
{.width = 1, .icon = &I_ButtonF1_5x8, .value = HID_KEYBOARD_F1},
|
||||
@ -140,17 +140,17 @@ const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = {
|
||||
{.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW},
|
||||
},
|
||||
{
|
||||
{.width = 2, .icon = &I_Ctrl_15x7, .value = HID_KEYBOARD_L_CTRL},
|
||||
{.width = 2, .icon = &I_Ctrl_17x10, .value = HID_KEYBOARD_L_CTRL},
|
||||
{.width = 0, .value = HID_KEYBOARD_L_CTRL},
|
||||
{.width = 2, .icon = &I_Alt_11x7, .value = HID_KEYBOARD_L_ALT},
|
||||
{.width = 2, .icon = &I_Alt_17x10, .value = HID_KEYBOARD_L_ALT},
|
||||
{.width = 0, .value = HID_KEYBOARD_L_ALT},
|
||||
{.width = 2, .icon = &I_Cmd_15x7, .value = HID_KEYBOARD_L_GUI},
|
||||
{.width = 2, .icon = &I_Cmd_17x10, .value = HID_KEYBOARD_L_GUI},
|
||||
{.width = 0, .value = HID_KEYBOARD_L_GUI},
|
||||
{.width = 2, .icon = &I_Tab_15x7, .value = HID_KEYBOARD_TAB},
|
||||
{.width = 2, .icon = &I_Tab_17x10, .value = HID_KEYBOARD_TAB},
|
||||
{.width = 0, .value = HID_KEYBOARD_TAB},
|
||||
{.width = 2, .icon = &I_Esc_14x7, .value = HID_KEYBOARD_ESCAPE},
|
||||
{.width = 2, .icon = &I_Esc_17x10, .value = HID_KEYBOARD_ESCAPE},
|
||||
{.width = 0, .value = HID_KEYBOARD_ESCAPE},
|
||||
{.width = 2, .icon = &I_Del_12x7, .value = HID_KEYBOARD_DELETE_FORWARD},
|
||||
{.width = 2, .icon = &I_Del_17x10, .value = HID_KEYBOARD_DELETE_FORWARD},
|
||||
{.width = 0, .value = HID_KEYBOARD_DELETE_FORWARD},
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
#include "storage_move_to_sd.h"
|
||||
|
||||
#include <core/common_defines.h>
|
||||
#include <core/log.h>
|
||||
#include "loader/loader.h"
|
||||
#include <stdint.h>
|
||||
#include <loader/loader.h>
|
||||
#include <toolbox/dir_walk.h>
|
||||
#include <toolbox/path.h>
|
||||
|
||||
|
||||
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_0.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_1.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_10.png
vendored
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_11.png
vendored
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_12.png
vendored
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_13.png
vendored
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_14.png
vendored
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_15.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_16.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_17.png
vendored
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_18.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_19.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_2.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_20.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_21.png
vendored
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_22.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_23.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_24.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_25.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_26.png
vendored
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_27.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_28.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_29.png
vendored
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_3.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_30.png
vendored
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_31.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_32.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_33.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_34.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_35.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_36.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_37.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_38.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_39.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_4.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_40.png
vendored
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_41.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_42.png
vendored
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_43.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_44.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_45.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_46.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_47.png
vendored
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_48.png
vendored
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_49.png
vendored
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_5.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_50.png
vendored
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_51.png
vendored
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_52.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_6.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/dolphin/external/L2_Secret_door_128x64/frame_7.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |