diff options
author | Nikolay Minaylov <nm29719@gmail.com> | 2021-12-22 23:04:08 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-22 23:04:08 +0300 |
commit | 9e62f08e4d6805e01a08f4def2bf4611d920046c (patch) | |
tree | 50bd51265cece455029190e0f517e315175095aa /applications/u2f | |
parent | 9b62b557b442ab88206b8b44253c1dcbac35c4e3 (diff) |
[FL-1958] U2F prototype (#879)
* U2F implementation prototype
* U2F data encryption and store, user confirmation request
* remove debug prints
* fix notification bug in chrome
* split u2f_alloc into u2f_init and u2f_alloc
* typo fix, furi-hal-trng -> furi-hal-random
* rand/srand redefinition
* SubGhz: a little bit of Dante.
* u2f_data naming fix
Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
Diffstat (limited to 'applications/u2f')
-rw-r--r-- | applications/u2f/scenes/u2f_scene.c | 30 | ||||
-rw-r--r-- | applications/u2f/scenes/u2f_scene.h | 29 | ||||
-rw-r--r-- | applications/u2f/scenes/u2f_scene_config.h | 1 | ||||
-rw-r--r-- | applications/u2f/scenes/u2f_scene_main.c | 92 | ||||
-rw-r--r-- | applications/u2f/u2f.c | 332 | ||||
-rw-r--r-- | applications/u2f/u2f.h | 35 | ||||
-rw-r--r-- | applications/u2f/u2f_app.c | 77 | ||||
-rw-r--r-- | applications/u2f/u2f_app.h | 11 | ||||
-rw-r--r-- | applications/u2f/u2f_app_i.h | 45 | ||||
-rw-r--r-- | applications/u2f/u2f_data.c | 382 | ||||
-rw-r--r-- | applications/u2f/u2f_data.h | 25 | ||||
-rw-r--r-- | applications/u2f/u2f_hid.c | 293 | ||||
-rw-r--r-- | applications/u2f/u2f_hid.h | 18 | ||||
-rw-r--r-- | applications/u2f/views/u2f_view.c | 91 | ||||
-rw-r--r-- | applications/u2f/views/u2f_view.h | 23 |
15 files changed, 1484 insertions, 0 deletions
diff --git a/applications/u2f/scenes/u2f_scene.c b/applications/u2f/scenes/u2f_scene.c new file mode 100644 index 00000000..92739fca --- /dev/null +++ b/applications/u2f/scenes/u2f_scene.c @@ -0,0 +1,30 @@ +#include "u2f_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const u2f_scene_on_enter_handlers[])(void*) = { +#include "u2f_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const u2f_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "u2f_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const u2f_scene_on_exit_handlers[])(void* context) = { +#include "u2f_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers u2f_scene_handlers = { + .on_enter_handlers = u2f_scene_on_enter_handlers, + .on_event_handlers = u2f_scene_on_event_handlers, + .on_exit_handlers = u2f_scene_on_exit_handlers, + .scene_num = U2fSceneNum, +}; diff --git a/applications/u2f/scenes/u2f_scene.h b/applications/u2f/scenes/u2f_scene.h new file mode 100644 index 00000000..1a9dcd75 --- /dev/null +++ b/applications/u2f/scenes/u2f_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include <gui/scene_manager.h> + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) U2fScene##id, +typedef enum { +#include "u2f_scene_config.h" + U2fSceneNum, +} U2fScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers u2f_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "u2f_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "u2f_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "u2f_scene_config.h" +#undef ADD_SCENE diff --git a/applications/u2f/scenes/u2f_scene_config.h b/applications/u2f/scenes/u2f_scene_config.h new file mode 100644 index 00000000..636589f2 --- /dev/null +++ b/applications/u2f/scenes/u2f_scene_config.h @@ -0,0 +1 @@ +ADD_SCENE(u2f, main, Main) diff --git a/applications/u2f/scenes/u2f_scene_main.c b/applications/u2f/scenes/u2f_scene_main.c new file mode 100644 index 00000000..6967a679 --- /dev/null +++ b/applications/u2f/scenes/u2f_scene_main.c @@ -0,0 +1,92 @@ +#include "../u2f_app_i.h" +#include "../views/u2f_view.h" +#include "furi-hal.h" +#include "../u2f.h" + +#define U2F_EVENT_TIMEOUT 500 + +static void u2f_scene_main_ok_callback(InputType type, void* context) { + furi_assert(context); + U2fApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, U2fCustomEventConfirm); +} + +static void u2f_scene_main_event_callback(U2fNotifyEvent evt, void* context) { + furi_assert(context); + U2fApp* app = context; + if(evt == U2fNotifyRegister) + view_dispatcher_send_custom_event(app->view_dispatcher, U2fCustomEventRegister); + else if(evt == U2fNotifyAuth) + view_dispatcher_send_custom_event(app->view_dispatcher, U2fCustomEventAuth); + else if(evt == U2fNotifyWink) + view_dispatcher_send_custom_event(app->view_dispatcher, U2fCustomEventWink); +} + +static void u2f_scene_main_timer_callback(void* context) { + furi_assert(context); + U2fApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, U2fCustomEventTimeout); +} + +bool u2f_scene_main_on_event(void* context, SceneManagerEvent event) { + furi_assert(context); + U2fApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if((event.event == U2fCustomEventRegister) || (event.event == U2fCustomEventAuth)) { + osTimerStart(app->timer, U2F_EVENT_TIMEOUT); + if(app->event_cur == U2fCustomEventNone) { + app->event_cur = event.event; + if(event.event == U2fCustomEventRegister) + u2f_view_set_state(app->u2f_view, U2fMsgRegister); + else if(event.event == U2fCustomEventAuth) + u2f_view_set_state(app->u2f_view, U2fMsgAuth); + notification_message(app->notifications, &sequence_success); + } + notification_message(app->notifications, &sequence_blink_blue_10); + } else if(event.event == U2fCustomEventWink) { + notification_message(app->notifications, &sequence_blink_green_10); + } else if(event.event == U2fCustomEventTimeout) { + app->event_cur = U2fCustomEventNone; + u2f_view_set_state(app->u2f_view, U2fMsgNone); + } else if(event.event == U2fCustomEventConfirm) { + if(app->event_cur != U2fCustomEventNone) { + u2f_confirm_user_present(app->u2f_instance); + } + } + + consumed = true; + } else if(event.type == SceneManagerEventTypeTick) { + } + return consumed; +} + +void u2f_scene_main_on_enter(void* context) { + U2fApp* app = context; + + app->timer = osTimerNew(u2f_scene_main_timer_callback, osTimerOnce, app, NULL); + + app->u2f_instance = u2f_alloc(); + app->u2f_ready = u2f_init(app->u2f_instance); + if(app->u2f_ready == true) { + u2f_set_event_callback(app->u2f_instance, u2f_scene_main_event_callback, app); + app->u2f_hid = u2f_hid_start(app->u2f_instance); + u2f_view_set_ok_callback(app->u2f_view, u2f_scene_main_ok_callback, app); + } else { + u2f_free(app->u2f_instance); + u2f_view_set_state(app->u2f_view, U2fMsgError); + } + + view_dispatcher_switch_to_view(app->view_dispatcher, U2fAppViewMain); +} + +void u2f_scene_main_on_exit(void* context) { + U2fApp* app = context; + osTimerStop(app->timer); + osTimerDelete(app->timer); + if(app->u2f_ready == true) { + u2f_hid_stop(app->u2f_hid); + u2f_free(app->u2f_instance); + } +} diff --git a/applications/u2f/u2f.c b/applications/u2f/u2f.c new file mode 100644 index 00000000..04c8e1df --- /dev/null +++ b/applications/u2f/u2f.c @@ -0,0 +1,332 @@ +#include <furi.h> +#include "u2f.h" +#include "u2f_hid.h" +#include "u2f_data.h" +#include <furi-hal.h> +#include <furi-hal-random.h> + +#include "toolbox/sha256.h" +#include "toolbox/hmac_sha256.h" +#include "micro-ecc/uECC.h" + +#define TAG "U2F" +#define WORKER_TAG TAG "Worker" + +#define U2F_CMD_REGISTER 0x01 +#define U2F_CMD_AUTHENTICATE 0x02 +#define U2F_CMD_VERSION 0x03 + +typedef enum { + U2fCheckOnly = 0x07, // "check-only" - only check key handle, don't send auth response + U2fEnforce = + 0x03, // "enforce-user-presence-and-sign" - send auth response only if user is present + U2fDontEnforce = + 0x08, // "dont-enforce-user-presence-and-sign" - send auth response even if user is missing +} U2fAuthMode; + +typedef struct { + uint8_t format; + uint8_t xy[64]; +} __attribute__((packed)) U2fPubKey; + +typedef struct { + uint8_t len; + uint8_t hash[32]; + uint8_t nonce[32]; +} __attribute__((packed)) U2fKeyHandle; + +typedef struct { + uint8_t cla; + uint8_t ins; + uint8_t p1; + uint8_t p2; + uint8_t len[3]; + uint8_t challenge[32]; + uint8_t app_id[32]; +} __attribute__((packed)) U2fRegisterReq; + +typedef struct { + uint8_t reserved; + U2fPubKey pub_key; + U2fKeyHandle key_handle; + uint8_t cert[]; +} __attribute__((packed)) U2fRegisterResp; + +typedef struct { + uint8_t cla; + uint8_t ins; + uint8_t p1; + uint8_t p2; + uint8_t len[3]; + uint8_t challenge[32]; + uint8_t app_id[32]; + U2fKeyHandle key_handle; +} __attribute__((packed)) U2fAuthReq; + +typedef struct { + uint8_t user_present; + uint32_t counter; + uint8_t signature[]; +} __attribute__((packed)) U2fAuthResp; + +static const uint8_t ver_str[] = {"U2F_V2"}; + +static const uint8_t state_no_error[] = {0x90, 0x00}; +static const uint8_t state_not_supported[] = {0x6D, 0x00}; +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]; + uint32_t counter; + const struct uECC_Curve_t* p_curve; + bool ready; + bool user_present; + U2fEvtCallback callback; + void* context; +}; + +static int u2f_uecc_random(uint8_t* dest, unsigned size) { + furi_hal_random_fill_buf(dest, size); + return 1; +} + +U2fData* u2f_alloc() { + return furi_alloc(sizeof(U2fData)); +} + +void u2f_free(U2fData* U2F) { + furi_assert(U2F); + free(U2F); +} + +bool u2f_init(U2fData* U2F) { + furi_assert(U2F); + + if(u2f_data_cert_check() == false) { + FURI_LOG_E(TAG, "Certificate load error"); + return false; + } + if(u2f_data_cert_key_load(U2F->cert_key) == false) { + FURI_LOG_E(TAG, "Certificate key load error"); + return false; + } + if(u2f_data_key_load(U2F->device_key) == false) { + FURI_LOG_W(TAG, "Key loading error, generating new"); + if(u2f_data_key_generate(U2F->device_key) == false) { + FURI_LOG_E(TAG, "Key write failed"); + return false; + } + } + if(u2f_data_cnt_read(&U2F->counter) == false) { + FURI_LOG_W(TAG, "Counter loading error, resetting counter"); + U2F->counter = 0; + if(u2f_data_cnt_write(0) == false) { + FURI_LOG_E(TAG, "Counter write failed"); + return false; + } + } + + U2F->p_curve = uECC_secp256r1(); + uECC_set_rng(u2f_uecc_random); + + U2F->ready = true; + return true; +} + +void u2f_set_event_callback(U2fData* U2F, U2fEvtCallback callback, void* context) { + furi_assert(U2F); + furi_assert(callback); + U2F->callback = callback; + U2F->context = context; +} + +void u2f_confirm_user_present(U2fData* U2F) { + U2F->user_present = true; +} + +static uint8_t u2f_der_encode_int(uint8_t* der, uint8_t* val, uint8_t val_len) { + der[0] = 0x02; // Integer + + uint8_t len = 2; + // Omit leading zeros + while(val[0] == 0 && val_len > 0) { + ++val; + --val_len; + } + + // Check if integer is negative + if(val[0] > 0x7f) der[len++] = 0; + + memcpy(der + len, val, val_len); + len += val_len; + + der[1] = len - 2; + return len; +} + +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); + + der[1] = len - 2; + return len; +} + +static uint16_t u2f_register(U2fData* U2F, uint8_t* buf) { + U2fRegisterReq* req = (U2fRegisterReq*)buf; + U2fRegisterResp* resp = (U2fRegisterResp*)buf; + U2fKeyHandle handle; + uint8_t private[32]; + U2fPubKey pub_key; + uint8_t hash[32]; + uint8_t signature[64]; + + if(U2F->callback != NULL) U2F->callback(U2fNotifyRegister, U2F->context); + if(U2F->user_present == false) { + memcpy(&buf[0], state_user_missing, 2); + return 2; + } + U2F->user_present = false; + + hmac_sha256_context hmac_ctx; + sha256_context sha_ctx; + + 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); + + // 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 public key + pub_key.format = 0x04; // Uncompressed point + uECC_compute_public_key(private, pub_key.xy, U2F->p_curve); + + // 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); + + uECC_sign(U2F->cert_key, hash, 32, signature, U2F->p_curve); + + // Encode response message + resp->reserved = 0x05; + memcpy(&(resp->pub_key), &pub_key, sizeof(U2fPubKey)); + memcpy(&(resp->key_handle), &handle, sizeof(U2fKeyHandle)); + uint32_t cert_len = u2f_data_cert_load(resp->cert); + uint8_t signature_len = u2f_der_encode_signature(resp->cert + cert_len, signature); + memcpy(resp->cert + cert_len + signature_len, state_no_error, 2); + + return (sizeof(U2fRegisterResp) + cert_len + signature_len + 2); +} + +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 mac_control[32]; + hmac_sha256_context hmac_ctx; + sha256_context sha_ctx; + uint8_t flags = 0; + uint8_t hash[32]; + uint8_t signature[64]; + + if(U2F->callback != NULL) U2F->callback(U2fNotifyAuth, U2F->context); + if(U2F->user_present == true) { + flags |= 1; + } else { + if(req->p1 == U2fEnforce) { + memcpy(&buf[0], state_user_missing, 2); + return 2; + } + } + U2F->user_present = false; + + // 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*)&(U2F->counter), 4); + sha256_update(&sha_ctx, req->challenge, 32); + sha256_finish(&sha_ctx, hash); + + // 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); + + // 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); + + if(memcmp(req->key_handle.hash, mac_control, 32) != 0) { + FURI_LOG_W(TAG, "Wrong handle!"); + memcpy(&buf[0], state_wrong_data, 2); + return 2; + } + + if(req->p1 == U2fCheckOnly) { // Check-only: don't need to send full response + memcpy(&buf[0], state_user_missing, 2); + return 2; + } + + uECC_sign(priv_key, hash, 32, signature, U2F->p_curve); + + resp->user_present = flags; + resp->counter = U2F->counter; + uint8_t signature_len = u2f_der_encode_signature(resp->signature, signature); + memcpy(resp->signature + signature_len, state_no_error, 2); + + FURI_LOG_I(TAG, "Counter: %lu", U2F->counter); + U2F->counter++; + u2f_data_cnt_write(U2F->counter); + + return (sizeof(U2fAuthResp) + signature_len + 2); +} + +uint16_t u2f_msg_parse(U2fData* U2F, uint8_t* buf, uint16_t len) { + furi_assert(U2F); + if(!U2F->ready) return 0; + if((buf[0] != 0x00) && (len < 5)) return 0; + if(buf[1] == U2F_CMD_REGISTER) { // Register request + return u2f_register(U2F, buf); + + } else if(buf[1] == U2F_CMD_AUTHENTICATE) { // Authenticate request + return u2f_authenticate(U2F, buf); + + } else if(buf[1] == U2F_CMD_VERSION) { // Get U2F version string + memcpy(&buf[0], ver_str, 6); + memcpy(&buf[6], state_no_error, 2); + return 8; + } else { + memcpy(&buf[0], state_not_supported, 2); + return 2; + } + return 0; +} + +void u2f_wink(U2fData* U2F) { + if(U2F->callback != NULL) U2F->callback(U2fNotifyWink, U2F->context); +} diff --git a/applications/u2f/u2f.h b/applications/u2f/u2f.h new file mode 100644 index 00000000..5e7d7b32 --- /dev/null +++ b/applications/u2f/u2f.h @@ -0,0 +1,35 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include <furi.h> + +typedef enum { + U2fNotifyRegister, + U2fNotifyAuth, + U2fNotifyWink, +} U2fNotifyEvent; + +typedef struct U2fData U2fData; + +typedef void (*U2fEvtCallback)(U2fNotifyEvent evt, void* context); + +U2fData* u2f_alloc(); + +bool u2f_init(U2fData* instance); + +void u2f_free(U2fData* instance); + +void u2f_set_event_callback(U2fData* instance, U2fEvtCallback callback, void* context); + +void u2f_confirm_user_present(U2fData* instance); + +uint16_t u2f_msg_parse(U2fData* instance, uint8_t* buf, uint16_t len); + +void u2f_wink(U2fData* instance); + +#ifdef __cplusplus +} +#endif diff --git a/applications/u2f/u2f_app.c b/applications/u2f/u2f_app.c new file mode 100644 index 00000000..3a096b7e --- /dev/null +++ b/applications/u2f/u2f_app.c @@ -0,0 +1,77 @@ +#include "u2f_app_i.h" +#include <furi.h> +#include <furi-hal.h> + +static bool u2f_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + U2fApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool u2f_app_back_event_callback(void* context) { + furi_assert(context); + U2fApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void u2f_app_tick_event_callback(void* context) { + furi_assert(context); + U2fApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +U2fApp* u2f_app_alloc() { + U2fApp* app = furi_alloc(sizeof(U2fApp)); + + app->gui = furi_record_open("gui"); + app->notifications = furi_record_open("notification"); + + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&u2f_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, u2f_app_tick_event_callback, 500); + + view_dispatcher_set_custom_event_callback(app->view_dispatcher, u2f_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, u2f_app_back_event_callback); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + app->u2f_view = u2f_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, U2fAppViewMain, u2f_view_get_view(app->u2f_view)); + + scene_manager_next_scene(app->scene_manager, U2fAppViewMain); + + return app; +} + +void u2f_app_free(U2fApp* app) { + furi_assert(app); + + // Views + view_dispatcher_remove_view(app->view_dispatcher, U2fAppViewMain); + u2f_view_free(app->u2f_view); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Close records + furi_record_close("gui"); + furi_record_close("notification"); + + free(app); +} + +int32_t u2f_app(void* p) { + U2fApp* u2f_app = u2f_app_alloc(); + + view_dispatcher_run(u2f_app->view_dispatcher); + + u2f_app_free(u2f_app); + + return 0; +} diff --git a/applications/u2f/u2f_app.h b/applications/u2f/u2f_app.h new file mode 100644 index 00000000..b19e0cca --- /dev/null +++ b/applications/u2f/u2f_app.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct U2fApp U2fApp; + +#ifdef __cplusplus +} +#endif diff --git a/applications/u2f/u2f_app_i.h b/applications/u2f/u2f_app_i.h new file mode 100644 index 00000000..6a30286e --- /dev/null +++ b/applications/u2f/u2f_app_i.h @@ -0,0 +1,45 @@ +#pragma once + +#include "u2f_app.h" +#include "scenes/u2f_scene.h" + +#include <gui/gui.h> +#include <gui/view_dispatcher.h> +#include <gui/scene_manager.h> +#include <gui/modules/submenu.h> +#include <dialogs/dialogs.h> +#include <notification/notification-messages.h> +#include <gui/modules/variable-item-list.h> +#include "views/u2f_view.h" +#include "u2f_hid.h" +#include "u2f.h" + +typedef enum { + U2fCustomEventNone, + + U2fCustomEventRegister, + U2fCustomEventAuth, + U2fCustomEventWink, + + U2fCustomEventTimeout, + + U2fCustomEventConfirm, + +} GpioCustomEvent; + +typedef enum { + U2fAppViewMain, +} U2fAppView; + +struct U2fApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + NotificationApp* notifications; + osTimerId_t timer; + U2fHid* u2f_hid; + U2fView* u2f_view; + U2fData* u2f_instance; + GpioCustomEvent event_cur; + bool u2f_ready; +}; diff --git a/applications/u2f/u2f_data.c b/applications/u2f/u2f_data.c new file mode 100644 index 00000000..038918a7 --- /dev/null +++ b/applications/u2f/u2f_data.c @@ -0,0 +1,382 @@ +#include <furi.h> +#include "u2f_hid.h" +#include <furi-hal.h> +#include <storage/storage.h> +#include <furi-hal-random.h> +#include <flipper_file.h> + +#define TAG "U2F" + +#define U2F_DATA_FOLDER "/any/u2f/" +#define U2F_CERT_FILE U2F_DATA_FOLDER "cert.der" +#define U2F_CERT_KEY_FILE U2F_DATA_FOLDER "cert_key.u2f" +#define U2F_KEY_FILE U2F_DATA_FOLDER "key.u2f" +#define U2F_CNT_FILE U2F_DATA_FOLDER "cnt.u2f" + +#define U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_FACTORY 2 +#define U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE 11 + +#define U2F_CERT_STOCK 0 // Stock certificate, private key is encrypted with factory key +#define U2F_CERT_USER 1 // User certificate, private key is encrypted with unique key + +#define U2F_CERT_KEY_FILE_TYPE "Flipper U2F Certificate Key File" +#define U2F_CERT_KEY_VERSION 1 + +#define U2F_DEVICE_KEY_FILE_TYPE "Flipper U2F Device Key File" +#define U2F_DEVICE_KEY_VERSION 1 + +#define U2F_COUNTER_FILE_TYPE "Flipper U2F Counter File" +#define U2F_COUNTER_VERSION 1 + +#define U2F_COUNTER_CONTROL_VAL 0xAA5500FF + +typedef struct { + uint32_t counter; + uint8_t random_salt[24]; + uint32_t control; +} __attribute__((packed)) U2fCounterData; + +bool u2f_data_cert_check() { + bool state = false; + Storage* fs_api = furi_record_open("storage"); + File* file = storage_file_alloc(fs_api); + uint8_t file_buf[8]; + + if(storage_file_open(file, U2F_CERT_FILE, FSAM_READ, FSOM_OPEN_EXISTING)) { + do { + // Read header to check certificate size + size_t file_size = storage_file_size(file); + size_t len_cur = storage_file_read(file, file_buf, 4); + if(len_cur != 4) break; + + if(file_buf[0] != 0x30) { + FURI_LOG_E(TAG, "Wrong certificate header"); + break; + } + + size_t temp_len = ((file_buf[2] << 8) | (file_buf[3])) + 4; + if(temp_len != file_size) { + FURI_LOG_E(TAG, "Wrong certificate length"); + break; + } + state = true; + } while(0); + } + + storage_file_close(file); + storage_file_free(file); + + furi_record_close("storage"); + + return state; +} + +uint32_t u2f_data_cert_load(uint8_t* cert) { + furi_assert(cert); + + Storage* fs_api = furi_record_open("storage"); + File* file = storage_file_alloc(fs_api); + uint32_t file_size = 0; + uint32_t len_cur = 0; + + if(storage_file_open(file, U2F_CERT_FILE, FSAM_READ, FSOM_OPEN_EXISTING)) { + file_size = storage_file_size(file); + len_cur = storage_file_read(file, cert, file_size); + if(len_cur != file_size) len_cur = 0; + } + + storage_file_close(file); + storage_file_free(file); + furi_record_close("storage"); + + return len_cur; +} + +bool u2f_data_cert_key_load(uint8_t* cert_key) { + furi_assert(cert_key); + + bool state = false; + uint8_t iv[16]; + uint8_t key[48]; + uint32_t cert_type = 0; + uint8_t key_slot = 0; + uint32_t version = 0; + + // Check if unique key exists in secure eclave and generate it if missing + if(!furi_hal_crypto_verify_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE)) return false; + + string_t filetype; + string_init(filetype); + + Storage* storage = furi_record_open("storage"); + FlipperFile* flipper_file = flipper_file_alloc(storage); + + if(flipper_file_open_existing(flipper_file, U2F_CERT_KEY_FILE)) { + do { + if(!flipper_file_read_header(flipper_file, filetype, &version)) { + FURI_LOG_E(TAG, "Missing or incorrect header"); + break; + } + + if(strcmp(string_get_cstr(filetype), U2F_CERT_KEY_FILE_TYPE) != 0 || + version != U2F_CERT_KEY_VERSION) { + FURI_LOG_E(TAG, "Type or version mismatch"); + break; + } + + if(!flipper_file_read_uint32(flipper_file, "Type", &cert_type, 1)) { + FURI_LOG_E(TAG, "Missing cert type"); + break; + } + + if(cert_type == U2F_CERT_STOCK) { + key_slot = U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_FACTORY; + } else if(cert_type == U2F_CERT_USER) { + key_slot = U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE; + } else { + FURI_LOG_E(TAG, "Unknown cert type"); + break; + } + + if(!flipper_file_read_hex(flipper_file, "IV", iv, 16)) { + FURI_LOG_E(TAG, "Missing IV"); + break; + } + + if(!flipper_file_read_hex(flipper_file, "Data", key, 48)) { + FURI_LOG_E(TAG, "Missing data"); + break; + } + + if(!furi_hal_crypto_store_load_key(key_slot, iv)) { + FURI_LOG_E(TAG, "Unable to load encryption key"); + break; + } + memset(cert_key, 0, 32); + + if(!furi_hal_crypto_decrypt(key, cert_key, 32)) { + memset(cert_key, 0, 32); + FURI_LOG_E(TAG, "Decryption failed"); + break; + } + furi_hal_crypto_store_unload_key(key_slot); + state = true; + } while(0); + } + + flipper_file_close(flipper_file); + flipper_file_free(flipper_file); + furi_record_close("storage"); + string_clear(filetype); + + return state; +} + +bool u2f_data_key_load(uint8_t* device_key) { + furi_assert(device_key); + + bool state = false; + uint8_t iv[16]; + uint8_t key[48]; + uint32_t version = 0; + + string_t filetype; + string_init(filetype); + + Storage* storage = furi_record_open("storage"); + FlipperFile* flipper_file = flipper_file_alloc(storage); + + if(flipper_file_open_existing(flipper_file, U2F_KEY_FILE)) { + do { + if(!flipper_file_read_header(flipper_file, filetype, &version)) { + FURI_LOG_E(TAG, "Missing or incorrect header"); + break; + } + if(strcmp(string_get_cstr(filetype), U2F_DEVICE_KEY_FILE_TYPE) != 0 || + version != U2F_DEVICE_KEY_VERSION) { + FURI_LOG_E(TAG, "Type or version mismatch"); + break; + } + if(!flipper_file_read_hex(flipper_file, "IV", iv, 16)) { + FURI_LOG_E(TAG, "Missing IV"); + break; + } + if(!flipper_file_read_hex(flipper_file, "Data", key, 48)) { + FURI_LOG_E(TAG, "Missing data"); + break; + } + if(!furi_hal_crypto_store_load_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE, iv)) { + FURI_LOG_E(TAG, "Unable to load encryption key"); + break; + } + memset(device_key, 0, 32); + if(!furi_hal_crypto_decrypt(key, device_key, 32)) { + memset(device_key, 0, 32); + FURI_LOG_E(TAG, "Decryption failed"); + break; + } + furi_hal_crypto_store_unload_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE); + state = true; + } while(0); + } + flipper_file_close(flipper_file); + flipper_file_free(flipper_file); + furi_record_close("storage"); + string_clear(filetype); + return state; +} + +bool u2f_data_key_generate(uint8_t* device_key) { + furi_assert(device_key); + + bool state = false; + uint8_t iv[16]; + uint8_t key[32]; + uint8_t key_encrypted[48]; + + // Generate random IV and key + furi_hal_random_fill_buf(iv, 16); + furi_hal_random_fill_buf(key, 32); + + if(!furi_hal_crypto_store_load_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE, iv)) { + FURI_LOG_E(TAG, "Unable to load encryption key"); + return false; + } + + if(!furi_hal_crypto_encrypt(key, key_encrypted, 32)) { + FURI_LOG_E(TAG, "Encryption failed"); + return false; + } + furi_hal_crypto_store_unload_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE); + + string_t filetype; + string_init(filetype); + + Storage* storage = furi_record_open("storage"); + FlipperFile* flipper_file = flipper_file_alloc(storage); + + if(flipper_file_open_always(flipper_file, U2F_KEY_FILE)) { + do { + if(!flipper_file_write_header_cstr( + flipper_file, U2F_DEVICE_KEY_FILE_TYPE, U2F_DEVICE_KEY_VERSION)) + break; + if(!flipper_file_write_hex(flipper_file, "IV", iv, 16)) break; + if(!flipper_file_write_hex(flipper_file, "Data", key_encrypted, 48)) break; + state = true; + memcpy(device_key, key, 32); + } while(0); + } + + flipper_file_close(flipper_file); + flipper_file_free(flipper_file); + furi_record_close("storage"); + string_clear(filetype); + + return state; +} + +bool u2f_data_cnt_read(uint32_t* cnt_val) { + furi_assert(cnt_val); + + bool state = false; + uint8_t iv[16]; + U2fCounterData cnt; + uint8_t cnt_encr[48]; + uint32_t version = 0; + + string_t filetype; + string_init(filetype); + + Storage* storage = furi_record_open("storage"); + FlipperFile* flipper_file = flipper_file_alloc(storage); + + if(flipper_file_open_existing(flipper_file, U2F_CNT_FILE)) { + do { + if(!flipper_file_read_header(flipper_file, filetype, &version)) { + FURI_LOG_E(TAG, "Missing or incorrect header"); + break; + } + if(strcmp(string_get_cstr(filetype), U2F_COUNTER_FILE_TYPE) != 0 || + version != U2F_COUNTER_VERSION) { + FURI_LOG_E(TAG, "Type or version mismatch"); + break; + } + if(!flipper_file_read_hex(flipper_file, "IV", iv, 16)) { + FURI_LOG_E(TAG, "Missing IV"); + break; + } + if(!flipper_file_read_hex(flipper_file, "Data", cnt_encr, 48)) { + FURI_LOG_E(TAG, "Missing data"); + break; + } + if(!furi_hal_crypto_store_load_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE, iv)) { + FURI_LOG_E(TAG, "Unable to load encryption key"); + break; + } + memset(&cnt, 0, 32); + if(!furi_hal_crypto_decrypt(cnt_encr, (uint8_t*)&cnt, 32)) { + memset(&cnt, 0, 32); + FURI_LOG_E(TAG, "Decryption failed"); + break; + } + furi_hal_crypto_store_unload_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE); + if(cnt.control == U2F_COUNTER_CONTROL_VAL) { + *cnt_val = cnt.counter; + state = true; + } + } while(0); + } + flipper_file_close(flipper_file); + flipper_file_free(flipper_file); + furi_record_close("storage"); + string_clear(filetype); + return state; +} + +bool u2f_data_cnt_write(uint32_t cnt_val) { + bool state = false; + uint8_t iv[16]; + U2fCounterData cnt; + uint8_t cnt_encr[48]; + + // Generate random IV and key + furi_hal_random_fill_buf(iv, 16); + furi_hal_random_fill_buf(cnt.random_salt, 24); + cnt.control = U2F_COUNTER_CONTROL_VAL; + cnt.counter = cnt_val; + + if(!furi_hal_crypto_store_load_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE, iv)) { + FURI_LOG_E(TAG, "Unable to load encryption key"); + return false; + } + + if(!furi_hal_crypto_encrypt((uint8_t*)&cnt, cnt_encr, 32)) { + FURI_LOG_E(TAG, "Encryption failed"); + return false; + } + furi_hal_crypto_store_unload_key(U2F_DATA_FILE_ENCRYPTION_KEY_SLOT_UNIQUE); + + string_t filetype; + string_init(filetype); + + Storage* storage = furi_record_open("storage"); + FlipperFile* flipper_file = flipper_file_alloc(storage); + + if(flipper_file_open_always(flipper_file, U2F_CNT_FILE)) { + do { + if(!flipper_file_write_header_cstr( + flipper_file, U2F_COUNTER_FILE_TYPE, U2F_COUNTER_VERSION)) + break; + if(!flipper_file_write_hex(flipper_file, "IV", iv, 16)) break; + if(!flipper_file_write_hex(flipper_file, "Data", cnt_encr, 48)) break; + state = true; + } while(0); + } + + flipper_file_close(flipper_file); + flipper_file_free(flipper_file); + furi_record_close("storage"); + string_clear(filetype); + + return state; +} diff --git a/applications/u2f/u2f_data.h b/applications/u2f/u2f_data.h new file mode 100644 index 00000000..e47e26a1 --- /dev/null +++ b/applications/u2f/u2f_data.h @@ -0,0 +1,25 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include <furi.h> + +bool u2f_data_cert_check(); + +uint32_t u2f_data_cert_load(uint8_t* cert); + +bool u2f_data_cert_key_load(uint8_t* cert_key); + +bool u2f_data_key_load(uint8_t* device_key); + +bool u2f_data_key_generate(uint8_t* device_key); + +bool u2f_data_cnt_read(uint32_t* cnt); + +bool u2f_data_cnt_write(uint32_t cnt); + +#ifdef __cplusplus +} +#endif diff --git a/applications/u2f/u2f_hid.c b/applications/u2f/u2f_hid.c new file mode 100644 index 00000000..34767c42 --- /dev/null +++ b/applications/u2f/u2f_hid.c @@ -0,0 +1,293 @@ +#include <furi.h> +#include "u2f_hid.h" +#include "u2f.h" +#include <furi-hal.h> +#include <gui/gui.h> +#include <input/input.h> +#include <lib/toolbox/args.h> +#include <furi-hal-usb-hid-u2f.h> +#include <storage/storage.h> + +#include <furi-hal-console.h> + +#define TAG "U2FHID" +#define WORKER_TAG TAG "Worker" + +#define U2F_HID_MAX_PAYLOAD_LEN ((HID_U2F_PACKET_LEN - 7) + 128 * (HID_U2F_PACKET_LEN - 5)) + +#define U2F_HID_TYPE_MASK 0x80 // Frame type mask +#define U2F_HID_TYPE_INIT 0x80 // Initial frame identifier +#define U2F_HID_TYPE_CONT 0x00 // Continuation frame identifier + +#define U2F_HID_PING (U2F_HID_TYPE_INIT | 0x01) // Echo data through local processor only +#define U2F_HID_MSG (U2F_HID_TYPE_INIT | 0x03) // Send U2F message frame +#define U2F_HID_LOCK (U2F_HID_TYPE_INIT | 0x04) // Send lock channel command +#define U2F_HID_INIT (U2F_HID_TYPE_INIT | 0x06) // Channel initialization +#define U2F_HID_WINK (U2F_HID_TYPE_INIT | 0x08) // Send device identification wink +#define U2F_HID_ERROR (U2F_HID_TYPE_INIT | 0x3f) // Error response + +#define U2F_HID_ERR_NONE 0x00 // No error +#define U2F_HID_ERR_INVALID_CMD 0x01 // Invalid command +#define U2F_HID_ERR_INVALID_PAR 0x02 // Invalid parameter +#define U2F_HID_ERR_INVALID_LEN 0x03 // Invalid message length +#define U2F_HID_ERR_INVALID_SEQ 0x04 // Invalid message sequencing +#define U2F_HID_ERR_MSG_TIMEOUT 0x05 // Message has timed out +#define U2F_HID_ERR_CHANNEL_BUSY 0x06 // Channel busy +#define U2F_HID_ERR_LOCK_REQUIRED 0x0a // Command requires channel lock +#define U2F_HID_ERR_SYNC_FAIL 0x0b // SYNC command failed +#define U2F_HID_ERR_OTHER 0x7f // Other unspecified error + +#define U2F_HID_BROADCAST_CID 0xFFFFFFFF + +typedef enum { + WorkerEvtReserved = (1 << 0), + WorkerEvtStop = (1 << 1), + WorkerEvtConnect = (1 << 2), + WorkerEvtDisconnect = (1 << 3), + WorkerEvtRequest = (1 << 4), + WorkerEvtUnlock = (1 << 5), +} WorkerEvtFlags; + +struct U2fHid_packet { + uint32_t cid; + uint16_t len; + uint8_t cmd; + uint8_t payload[U2F_HID_MAX_PAYLOAD_LEN]; +}; + +struct U2fHid { + FuriThread* thread; + osTimerId_t lock_timer; + struct U2fHid_packet packet; + uint8_t seq_id_last; + uint16_t req_buf_ptr; + uint32_t req_len_left; + uint32_t lock_cid; + bool lock; + U2fData* u2f_instance; +}; + +static void u2f_hid_event_callback(HidU2fEvent ev, void* context) { + furi_assert(context); + U2fHid* u2f_hid = context; + + if(ev == HidU2fDisconnected) + osThreadFlagsSet(furi_thread_get_thread_id(u2f_hid->thread), WorkerEvtDisconnect); + else if(ev == HidU2fConnected) + osThreadFlagsSet(furi_thread_get_thread_id(u2f_hid->thread), WorkerEvtConnect); + else if(ev == HidU2fRequest) + osThreadFlagsSet(furi_thread_get_thread_id(u2f_hid->thread), WorkerEvtRequest); +} + +static void u2f_hid_lock_timeout_callback(void* context) { + furi_assert(context); + U2fHid* u2f_hid = context; + + osThreadFlagsSet(furi_thread_get_thread_id(u2f_hid->thread), WorkerEvtUnlock); +} + +static void u2f_hid_send_response(U2fHid* u2f_hid) { + uint8_t packet_buf[HID_U2F_PACKET_LEN]; + uint16_t len_remain = u2f_hid->packet.len; + uint8_t len_cur = 0; + uint8_t seq_cnt = 0; + uint16_t data_ptr = 0; + + memset(packet_buf, 0, HID_U2F_PACKET_LEN); + memcpy(packet_buf, &(u2f_hid->packet.cid), 4); + + // Init packet + packet_buf[4] = u2f_hid->packet.cmd; + packet_buf[5] = u2f_hid->packet.len >> 8; + packet_buf[6] = (u2f_hid->packet.len & 0xFF); + len_cur = (len_remain < (HID_U2F_PACKET_LEN - 7)) ? (len_remain) : (HID_U2F_PACKET_LEN - 7); + if(len_cur > 0) memcpy(&packet_buf[7], u2f_hid->packet.payload, len_cur); + furi_hal_hid_u2f_send_response(packet_buf, HID_U2F_PACKET_LEN); + data_ptr = len_cur; + len_remain -= len_cur; + + // Continuation packets + while(len_remain > 0) { + memset(&packet_buf[4], 0, HID_U2F_PACKET_LEN - 4); + packet_buf[4] = seq_cnt; + len_cur = (len_remain < (HID_U2F_PACKET_LEN - 5)) ? (len_remain) : + (HID_U2F_PACKET_LEN - 5); + memcpy(&packet_buf[5], &(u2f_hid->packet.payload[data_ptr]), len_cur); + furi_hal_hid_u2f_send_response(packet_buf, HID_U2F_PACKET_LEN); + seq_cnt++; + len_remain -= len_cur; + data_ptr += len_cur; + } +} + +static void u2f_hid_send_error(U2fHid* u2f_hid, uint8_t error) { + u2f_hid->packet.len = 1; + u2f_hid->packet.cmd = U2F_HID_ERROR; + u2f_hid->packet.payload[0] = error; + u2f_hid_send_response(u2f_hid); +} + +static bool u2f_hid_parse_request(U2fHid* u2f_hid) { + FURI_LOG_I( + WORKER_TAG, + "Req cid=%lX cmd=%x len=%u", + u2f_hid->packet.cid, + u2f_hid->packet.cmd, + u2f_hid->packet.len); + + if(u2f_hid->packet.cmd == U2F_HID_PING) { // PING - echo request back + u2f_hid_send_response(u2f_hid); + + } else if(u2f_hid->packet.cmd == U2F_HID_MSG) { // MSG - U2F message + if((u2f_hid->lock == true) && (u2f_hid->packet.cid != u2f_hid->lock_cid)) return false; + uint16_t resp_len = + u2f_msg_parse(u2f_hid->u2f_instance, u2f_hid->packet.payload, u2f_hid->packet.len); + if(resp_len > 0) { + u2f_hid->packet.len = resp_len; + u2f_hid_send_response(u2f_hid); + } else + return false; + + } else if(u2f_hid->packet.cmd == U2F_HID_LOCK) { // LOCK - lock all channels except current + if(u2f_hid->packet.len != 1) return false; + uint8_t lock_timeout = u2f_hid->packet.payload[0]; + if(lock_timeout == 0) { // Lock off + u2f_hid->lock = false; + u2f_hid->lock_cid = 0; + } else { // Lock on + u2f_hid->lock = true; + u2f_hid->lock_cid = u2f_hid->packet.cid; + osTimerStart(u2f_hid->lock_timer, lock_timeout * 1000); + } + + } else if(u2f_hid->packet.cmd == U2F_HID_INIT) { // INIT - channel initialization request + if((u2f_hid->packet.len != 8) || (u2f_hid->packet.cid != U2F_HID_BROADCAST_CID) || + (u2f_hid->lock == true)) + return false; + u2f_hid->packet.len = 17; + uint32_t random_cid = furi_hal_random_get(); + memcpy(&(u2f_hid->packet.payload[8]), &random_cid, 4); + u2f_hid->packet.payload[12] = 2; // Protocol version + u2f_hid->packet.payload[13] = 1; // Device version major + u2f_hid->packet.payload[14] = 0; // Device version minor + u2f_hid->packet.payload[15] = 1; // Device build version + u2f_hid->packet.payload[16] = 1; // Capabilities: wink + u2f_hid_send_response(u2f_hid); + + } else if(u2f_hid->packet.cmd == U2F_HID_WINK) { // WINK - notify user + if(u2f_hid->packet.len != 0) return false; + u2f_wink(u2f_hid->u2f_instance); + u2f_hid->packet.len = 0; + u2f_hid_send_response(u2f_hid); + } else + return false; + return true; +} + +static int32_t u2f_hid_worker(void* context) { + U2fHid* u2f_hid = context; + uint8_t packet_buf[HID_U2F_PACKET_LEN]; + + FURI_LOG_I(WORKER_TAG, "Init"); + + UsbInterface* usb_mode_prev = furi_hal_usb_get_config(); + furi_hal_usb_set_config(&usb_hid_u2f); + + u2f_hid->lock_timer = osTimerNew(u2f_hid_lock_timeout_callback, osTimerOnce, u2f_hid, NULL); + + furi_hal_hid_u2f_set_callback(u2f_hid_event_callback, u2f_hid); + + while(1) { + uint32_t flags = osThreadFlagsWait( + WorkerEvtStop | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtRequest, + osFlagsWaitAny, + osWaitForever); + furi_check((flags & osFlagsError) == 0); + if(flags & WorkerEvtStop) break; + if(flags & WorkerEvtConnect) FURI_LOG_I(WORKER_TAG, "Connect"); + if(flags & WorkerEvtDisconnect) FURI_LOG_I(WORKER_TAG, "Disconnect"); + if(flags & WorkerEvtRequest) { + uint32_t len_cur = furi_hal_hid_u2f_get_request(packet_buf); + if(len_cur > 0) { + if((packet_buf[4] & U2F_HID_TYPE_MASK) == U2F_HID_TYPE_INIT) { + // Init packet + u2f_hid->packet.len = (packet_buf[5] << 8) | (packet_buf[6]); + if(u2f_hid->packet.len > (len_cur - 7)) { + u2f_hid->req_len_left = u2f_hid->packet.len - (len_cur - 7); + len_cur = len_cur - 7; + } else { + u2f_hid->req_len_left = 0; + len_cur = u2f_hid->packet.len; + } + memcpy(&(u2f_hid->packet.cid), packet_buf, 4); + u2f_hid->packet.cmd = packet_buf[4]; + u2f_hid->seq_id_last = 0; + u2f_hid->req_buf_ptr = len_cur; + if(len_cur > 0) memcpy(u2f_hid->packet.payload, &packet_buf[7], len_cur); + } else { + // Continuation packet + if(u2f_hid->req_len_left > 0) { + uint32_t cid_temp = 0; + memcpy(&cid_temp, packet_buf, 4); + uint8_t seq_temp = packet_buf[4]; + if((cid_temp == u2f_hid->packet.cid) && + (seq_temp == u2f_hid->seq_id_last)) { + if(u2f_hid->req_len_left > (len_cur - 5)) { + len_cur = len_cur - 5; + u2f_hid->req_len_left -= len_cur; + } else { + len_cur = u2f_hid->req_len_left; + u2f_hid->req_len_left = 0; + } + memcpy( + &(u2f_hid->packet.payload[u2f_hid->req_buf_ptr]), + &packet_buf[5], + len_cur); + u2f_hid->req_buf_ptr += len_cur; + u2f_hid->seq_id_last++; + } + } + } + if(u2f_hid->req_len_left == 0) { + if(u2f_hid_parse_request(u2f_hid) == false) { + u2f_hid_send_error(u2f_hid, U2F_HID_ERR_INVALID_CMD); + } + } + } + } + if(flags & WorkerEvtUnlock) { + u2f_hid->lock = false; + u2f_hid->lock_cid = 0; + } + } + osTimerStop(u2f_hid->lock_timer); + osTimerDelete(u2f_hid->lock_timer); + + furi_hal_hid_u2f_set_callback(NULL, NULL); + furi_hal_usb_set_config(usb_mode_prev); + FURI_LOG_I(WORKER_TAG, "End"); + + return 0; +} + +U2fHid* u2f_hid_start(U2fData* u2f_inst) { + U2fHid* u2f_hid = furi_alloc(sizeof(U2fHid)); + + u2f_hid->u2f_instance = u2f_inst; + + u2f_hid->thread = furi_thread_alloc(); + furi_thread_set_name(u2f_hid->thread, "U2fHidWorker"); + furi_thread_set_stack_size(u2f_hid->thread, 2048); + furi_thread_set_context(u2f_hid->thread, u2f_hid); + furi_thread_set_callback(u2f_hid->thread, u2f_hid_worker); + furi_thread_start(u2f_hid->thread); + return u2f_hid; +} + +void u2f_hid_stop(U2fHid* u2f_hid) { + furi_assert(u2f_hid); + osThreadFlagsSet(furi_thread_get_thread_id(u2f_hid->thread), WorkerEvtStop); + furi_thread_join(u2f_hid->thread); + furi_thread_free(u2f_hid->thread); + free(u2f_hid); +} diff --git a/applications/u2f/u2f_hid.h b/applications/u2f/u2f_hid.h new file mode 100644 index 00000000..2b943007 --- /dev/null +++ b/applications/u2f/u2f_hid.h @@ -0,0 +1,18 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include <furi.h> +#include "u2f.h" + +typedef struct U2fHid U2fHid; + +U2fHid* u2f_hid_start(U2fData* u2f_inst); + +void u2f_hid_stop(U2fHid* u2f_hid); + +#ifdef __cplusplus +} +#endif diff --git a/applications/u2f/views/u2f_view.c b/applications/u2f/views/u2f_view.c new file mode 100644 index 00000000..b013638c --- /dev/null +++ b/applications/u2f/views/u2f_view.c @@ -0,0 +1,91 @@ +#include "u2f_view.h" +#include <gui/elements.h> + +struct U2fView { + View* view; + U2fOkCallback callback; + void* context; +}; + +typedef struct { + U2fViewMsg display_msg; +} U2fModel; + +static void u2f_view_draw_callback(Canvas* canvas, void* _model) { + U2fModel* model = _model; + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, "U2F Demo"); + + if(model->display_msg == U2fMsgRegister) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 0, 45, AlignLeft, AlignBottom, "Registration"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 0, 63, AlignLeft, AlignBottom, "Press [OK] to confirm"); + } else if(model->display_msg == U2fMsgAuth) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 0, 45, AlignLeft, AlignBottom, "Authentication"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 0, 63, AlignLeft, AlignBottom, "Press [OK] to confirm"); + } else if(model->display_msg == U2fMsgError) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, "U2F data missing"); + } +} + +static bool u2f_view_input_callback(InputEvent* event, void* context) { + furi_assert(context); + U2fView* u2f = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyOk) { + consumed = true; + if(u2f->callback != NULL) u2f->callback(InputTypeShort, u2f->context); + } + } + + return consumed; +} + +U2fView* u2f_view_alloc() { + U2fView* u2f = furi_alloc(sizeof(U2fView)); + + u2f->view = view_alloc(); + view_allocate_model(u2f->view, ViewModelTypeLocking, sizeof(U2fModel)); + view_set_context(u2f->view, u2f); + view_set_draw_callback(u2f->view, u2f_view_draw_callback); + view_set_input_callback(u2f->view, u2f_view_input_callback); + + return u2f; +} + +void u2f_view_free(U2fView* u2f) { + furi_assert(u2f); + view_free(u2f->view); + free(u2f); +} + +View* u2f_view_get_view(U2fView* u2f) { + furi_assert(u2f); + return u2f->view; +} + +void u2f_view_set_ok_callback(U2fView* u2f, U2fOkCallback callback, void* context) { + furi_assert(u2f); + furi_assert(callback); + with_view_model( + u2f->view, (U2fModel * model) { + u2f->callback = callback; + u2f->context = context; + return false; + }); +} + +void u2f_view_set_state(U2fView* u2f, U2fViewMsg msg) { + with_view_model( + u2f->view, (U2fModel * model) { + model->display_msg = msg; + return false; + }); +} diff --git a/applications/u2f/views/u2f_view.h b/applications/u2f/views/u2f_view.h new file mode 100644 index 00000000..a222fbc3 --- /dev/null +++ b/applications/u2f/views/u2f_view.h @@ -0,0 +1,23 @@ +#pragma once + +#include <gui/view.h> + +typedef struct U2fView U2fView; +typedef void (*U2fOkCallback)(InputType type, void* context); + +typedef enum { + U2fMsgNone, + U2fMsgRegister, + U2fMsgAuth, + U2fMsgError, +} U2fViewMsg; + +U2fView* u2f_view_alloc(); + +void u2f_view_free(U2fView* u2f); + +View* u2f_view_get_view(U2fView* u2f); + +void u2f_view_set_ok_callback(U2fView* u2f, U2fOkCallback callback, void* context); + +void u2f_view_set_state(U2fView* u2f, U2fViewMsg msg); |