Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/ClusterM/flipperzero-firmware.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikolay Minaylov <nm29719@gmail.com>2021-12-22 23:04:08 +0300
committerGitHub <noreply@github.com>2021-12-22 23:04:08 +0300
commit9e62f08e4d6805e01a08f4def2bf4611d920046c (patch)
tree50bd51265cece455029190e0f517e315175095aa /applications/u2f
parent9b62b557b442ab88206b8b44253c1dcbac35c4e3 (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.c30
-rw-r--r--applications/u2f/scenes/u2f_scene.h29
-rw-r--r--applications/u2f/scenes/u2f_scene_config.h1
-rw-r--r--applications/u2f/scenes/u2f_scene_main.c92
-rw-r--r--applications/u2f/u2f.c332
-rw-r--r--applications/u2f/u2f.h35
-rw-r--r--applications/u2f/u2f_app.c77
-rw-r--r--applications/u2f/u2f_app.h11
-rw-r--r--applications/u2f/u2f_app_i.h45
-rw-r--r--applications/u2f/u2f_data.c382
-rw-r--r--applications/u2f/u2f_data.h25
-rw-r--r--applications/u2f/u2f_hid.c293
-rw-r--r--applications/u2f/u2f_hid.h18
-rw-r--r--applications/u2f/views/u2f_view.c91
-rw-r--r--applications/u2f/views/u2f_view.h23
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);