[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>
This commit is contained in:
@@ -20,6 +20,7 @@ extern int32_t desktop_srv(void* p);
|
||||
extern int32_t accessor_app(void* p);
|
||||
extern int32_t archive_app(void* p);
|
||||
extern int32_t bad_usb_app(void* p);
|
||||
extern int32_t u2f_app(void* p);
|
||||
extern int32_t uart_echo_app(void* p);
|
||||
extern int32_t blink_test_app(void* p);
|
||||
extern int32_t bt_debug_app(void* p);
|
||||
@@ -154,6 +155,11 @@ const FlipperApplication FLIPPER_APPS[] = {
|
||||
#ifdef APP_BAD_USB
|
||||
{.app = bad_usb_app, .name = "Bad USB", .stack_size = 2048, .icon = &A_BadUsb_14},
|
||||
#endif
|
||||
|
||||
#ifdef APP_U2F
|
||||
{.app = u2f_app, .name = "U2F", .stack_size = 2048, .icon = &A_U2F_14},
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
const size_t FLIPPER_APPS_COUNT = sizeof(FLIPPER_APPS) / sizeof(FlipperApplication);
|
||||
|
@@ -50,6 +50,7 @@ APP_DISPLAY_TEST = 1
|
||||
APP_BLE_HID = 1
|
||||
APP_USB_MOUSE = 1
|
||||
APP_BAD_USB = 1
|
||||
APP_U2F = 1
|
||||
APP_UART_ECHO = 1
|
||||
endif
|
||||
|
||||
@@ -165,7 +166,13 @@ APP_BAD_USB ?= 0
|
||||
ifeq ($(APP_BAD_USB), 1)
|
||||
CFLAGS += -DAPP_BAD_USB
|
||||
SRV_GUI = 1
|
||||
endif
|
||||
endif
|
||||
|
||||
APP_U2F ?= 0
|
||||
ifeq ($(APP_U2F), 1)
|
||||
CFLAGS += -DAPP_U2F
|
||||
SRV_GUI = 1
|
||||
endif
|
||||
|
||||
APP_BLE_HID ?=0
|
||||
ifeq ($(APP_BLE_HID), 1)
|
||||
|
@@ -35,7 +35,7 @@ void usb_test_submenu_callback(void* context, uint32_t index) {
|
||||
} else if(index == UsbTestSubmenuIndexHid) {
|
||||
furi_hal_usb_set_config(&usb_hid);
|
||||
} else if(index == UsbTestSubmenuIndexHidU2F) {
|
||||
//furi_hal_usb_set_config(UsbModeU2F);
|
||||
furi_hal_usb_set_config(&usb_hid_u2f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ UsbTestApp* usb_test_app_alloc() {
|
||||
submenu_add_item(
|
||||
app->submenu, "HID KB+Mouse", UsbTestSubmenuIndexHid, usb_test_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
app->submenu, "TODO: HID U2F", UsbTestSubmenuIndexHidU2F, usb_test_submenu_callback, app);
|
||||
app->submenu, "HID U2F", UsbTestSubmenuIndexHidU2F, usb_test_submenu_callback, app);
|
||||
view_set_previous_callback(submenu_get_view(app->submenu), usb_test_exit);
|
||||
view_dispatcher_add_view(app->view_dispatcher, 0, submenu_get_view(app->submenu));
|
||||
|
||||
|
@@ -1,7 +1,4 @@
|
||||
/*
|
||||
* Give up hope, everyone who enters here!!!
|
||||
* Оставь надежду, всяк сюда входящий!!!
|
||||
*/
|
||||
/* Abandon hope, all ye who enter here. */
|
||||
|
||||
#include "subghz_i.h"
|
||||
#include <lib/toolbox/path.h>
|
||||
@@ -329,4 +326,4 @@ int32_t subghz_app(void* p) {
|
||||
subghz_free(subghz);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
30
applications/u2f/scenes/u2f_scene.c
Normal file
30
applications/u2f/scenes/u2f_scene.c
Normal file
@@ -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,
|
||||
};
|
29
applications/u2f/scenes/u2f_scene.h
Normal file
29
applications/u2f/scenes/u2f_scene.h
Normal file
@@ -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
|
1
applications/u2f/scenes/u2f_scene_config.h
Normal file
1
applications/u2f/scenes/u2f_scene_config.h
Normal file
@@ -0,0 +1 @@
|
||||
ADD_SCENE(u2f, main, Main)
|
92
applications/u2f/scenes/u2f_scene_main.c
Normal file
92
applications/u2f/scenes/u2f_scene_main.c
Normal file
@@ -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);
|
||||
}
|
||||
}
|
332
applications/u2f/u2f.c
Normal file
332
applications/u2f/u2f.c
Normal file
@@ -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);
|
||||
}
|
35
applications/u2f/u2f.h
Normal file
35
applications/u2f/u2f.h
Normal file
@@ -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
|
77
applications/u2f/u2f_app.c
Normal file
77
applications/u2f/u2f_app.c
Normal file
@@ -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;
|
||||
}
|
11
applications/u2f/u2f_app.h
Normal file
11
applications/u2f/u2f_app.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct U2fApp U2fApp;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
45
applications/u2f/u2f_app_i.h
Normal file
45
applications/u2f/u2f_app_i.h
Normal file
@@ -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;
|
||||
};
|
382
applications/u2f/u2f_data.c
Normal file
382
applications/u2f/u2f_data.c
Normal file
@@ -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;
|
||||
}
|
25
applications/u2f/u2f_data.h
Normal file
25
applications/u2f/u2f_data.h
Normal file
@@ -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
|
293
applications/u2f/u2f_hid.c
Normal file
293
applications/u2f/u2f_hid.c
Normal file
@@ -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);
|
||||
}
|
18
applications/u2f/u2f_hid.h
Normal file
18
applications/u2f/u2f_hid.h
Normal file
@@ -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
|
91
applications/u2f/views/u2f_view.c
Normal file
91
applications/u2f/views/u2f_view.c
Normal file
@@ -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;
|
||||
});
|
||||
}
|
23
applications/u2f/views/u2f_view.h
Normal file
23
applications/u2f/views/u2f_view.h
Normal file
@@ -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);
|
Reference in New Issue
Block a user