فهرست منبع

update apps and add new apps

MX 2 سال پیش
والد
کامیت
3d11c31b4f

+ 13 - 6
README.md

@@ -5,8 +5,7 @@ feature that is available on the CLI. In addition it allows for basic
 encryption of messages.
 
 The plugin has been tested on the official firmware (version 0.87.0) and on
-Unleashed (version unlshd-059). Due to limitations of the official firmware,
-the behavior is slightly different there.
+Unleashed (version unlshd-059).
 
 Currently the use of an external antenna is not supported.
 
@@ -28,9 +27,8 @@ the key from another Flipper via NFC are supported.
 
 Finally the a message can be input. After the message is confirmed, the plugin
 will switch to the chat view, where sent and received messages are displayed.
-To view the chat view without entering a message, enter nothing (on Unleashed)
-or a single space (on OFW). To go back to entering a message press the back
-button.
+To view the chat view without entering a message, enter nothing. To go back to
+entering a message press the back button.
 
 In the chat view the keyboard can be locked by pressing and holding the OK
 button for a few seconds. To unlock the keyboard again quickly press the back
@@ -55,6 +53,14 @@ Messages are encrypted using 256 bit AES in GCM mode. Each message gets its own
 random IV. On reception the tag generated by GCM is verified and the message
 discarded if it doesn't match.
 
+A simple mechanism to prevent replay attacks is implemented. Each time the app
+enters an encrypted chat an ID is generated by SHA-256 hashing the Flipper's
+name with the current system ticks. Each message is prefixed with this ID and a
+counter. A receiving Flipper will check if the counter on a received message is
+higher than the last counter received with this ID. If it is not, the message
+is discarded and an error is displayed. ID and counter are included in the GCM
+tag's computation and are therefore authenticated with the used key.
+
 If a password is used, the key for the encryption is derived from the password
 by applying SHA-256 to the password once.
 
@@ -79,4 +85,5 @@ The implementations of AES and GCM are taken directly from
 https://github.com/mko-x/SharedAES-GCM. They were released to the public domain
 by Markus Kosmal.
 
-The app icon was made by [xMasterX](https://github.com/xMasterX).
+The app icon was made by [xMasterX](https://github.com/xMasterX). Other icons
+and graphics were taken from the Flipper Zero firmware.

BIN
assets/Cry_dolph_55x52.png


BIN
assets/DolphinNice_96x59.png


+ 105 - 19
crypto_wrapper.c

@@ -1,4 +1,6 @@
 #include <furi_hal.h>
+#include <lib/mlib/m-dict.h>
+#include <toolbox/sha256.h>
 
 #ifndef FURI_HAL_CRYPTO_ADVANCED_AVAIL
 #include "crypto/gcm.h"
@@ -6,13 +8,26 @@
 
 #include "crypto_wrapper.h"
 
+DICT_DEF2(ESubGhzChatReplayDict, uint64_t, uint32_t)
+
 struct ESugGhzChatCryptoCtx {
 	uint8_t key[KEY_BITS / 8];
 #ifndef FURI_HAL_CRYPTO_ADVANCED_AVAIL
 	gcm_context gcm_ctx;
 #endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+	ESubGhzChatReplayDict_t replay_dict;
+	uint64_t run_id;
+	uint32_t counter;
 };
 
+struct ESubGhzChatCryptoMsg {
+	uint64_t run_id;
+	uint32_t counter;
+	uint8_t iv[IV_BYTES];
+	uint8_t tag[TAG_BYTES];
+	uint8_t data[0];
+} __attribute__ ((packed));
+
 void crypto_init(void)
 {
 #ifndef FURI_HAL_CRYPTO_ADVANCED_AVAIL
@@ -33,6 +48,9 @@ ESubGhzChatCryptoCtx *crypto_ctx_alloc(void)
 
 	if (ret != NULL) {
 		memset(ret, 0, sizeof(ESubGhzChatCryptoCtx));
+		ESubGhzChatReplayDict_init(ret->replay_dict);
+		ret->run_id = 0;
+		ret->counter = 1;
 	}
 
 	return ret;
@@ -41,16 +59,45 @@ ESubGhzChatCryptoCtx *crypto_ctx_alloc(void)
 void crypto_ctx_free(ESubGhzChatCryptoCtx *ctx)
 {
 	crypto_ctx_clear(ctx);
+	ESubGhzChatReplayDict_clear(ctx->replay_dict);
 	free(ctx);
 }
 
 void crypto_ctx_clear(ESubGhzChatCryptoCtx *ctx)
 {
-	crypto_explicit_bzero(ctx, sizeof(ESubGhzChatCryptoCtx));
+	crypto_explicit_bzero(ctx->key, sizeof(ctx->key));
+#ifndef FURI_HAL_CRYPTO_ADVANCED_AVAIL
+	crypto_explicit_bzero(&(ctx->gcm_ctx), sizeof(ctx->gcm_ctx));
+#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+	ESubGhzChatReplayDict_reset(ctx->replay_dict);
+	ctx->run_id = 0;
+	ctx->counter = 1;
 }
 
-bool crypto_ctx_set_key(ESubGhzChatCryptoCtx *ctx, const uint8_t *key)
+static uint64_t crypto_calc_run_id(FuriString *flipper_name, uint32_t tick)
 {
+	const char *fn = furi_string_get_cstr(flipper_name);
+	size_t fn_len = strlen(fn);
+
+	uint8_t h_in[fn_len + sizeof(uint32_t)];
+	memcpy(h_in, fn, fn_len);
+	memcpy(h_in + fn_len, &tick, sizeof(uint32_t));
+
+	uint8_t h_out[256];
+	sha256(h_in, fn_len + sizeof(uint32_t), h_out);
+
+	uint64_t run_id;
+	memcpy(&run_id, h_out, sizeof(uint64_t));
+
+	return run_id;
+}
+
+bool crypto_ctx_set_key(ESubGhzChatCryptoCtx *ctx, const uint8_t *key,
+		FuriString *flipper_name, uint32_t tick)
+{
+	ctx->run_id = crypto_calc_run_id(flipper_name, tick);
+	ctx->counter = 1;
+
 	memcpy(ctx->key, key, KEY_BITS / 8);
 #ifdef FURI_HAL_CRYPTO_ADVANCED_AVAIL
 	return true;
@@ -71,35 +118,74 @@ bool crypto_ctx_decrypt(ESubGhzChatCryptoCtx *ctx, uint8_t *in, size_t in_len,
 		return false;
 	}
 
+	struct ESubGhzChatCryptoMsg *msg = (struct ESubGhzChatCryptoMsg *) in;
+
+	// check if message is stale, if yes, discard
+	uint32_t *counter = ESubGhzChatReplayDict_get(ctx->replay_dict,
+			msg->run_id);
+	if (counter != NULL) {
+		if (*counter >= __ntohl(msg->counter)) {
+			return false;
+		}
+	}
+
+	// decrypt and auth message
 #ifdef FURI_HAL_CRYPTO_ADVANCED_AVAIL
-	return (furi_hal_crypto_gcm_decrypt_and_verify(ctx->key,
-			in, in + IV_BYTES, out,
+	bool ret = (furi_hal_crypto_gcm_decrypt_and_verify(ctx->key,
+			msg->iv,
+			(uint8_t *) msg, RUN_ID_BYTES + COUNTER_BYTES,
+			msg->data, out,
 			in_len - MSG_OVERHEAD,
-			in + in_len - TAG_BYTES) == FuriHalCryptoGCMStateOk);
+			msg->tag) == FuriHalCryptoGCMStateOk);
 #else /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
-	return (gcm_auth_decrypt(&(ctx->gcm_ctx),
-			in, IV_BYTES,
-			NULL, 0,
-			in + IV_BYTES, out, in_len - MSG_OVERHEAD,
-			in + in_len - TAG_BYTES, TAG_BYTES) == 0);
+	bool ret = (gcm_auth_decrypt(&(ctx->gcm_ctx),
+			msg->iv, IV_BYTES,
+			(uint8_t *) msg, RUN_ID_BYTES + COUNTER_BYTES,
+			msg->data, out,
+			in_len - MSG_OVERHEAD,
+			msg->tag, TAG_BYTES) == 0);
 #endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+
+	// if auth was successful update replay dict
+	if (ret) {
+		ESubGhzChatReplayDict_set_at(ctx->replay_dict, msg->run_id,
+				__ntohl(msg->counter));
+	}
+
+	return ret;
 }
 
 bool crypto_ctx_encrypt(ESubGhzChatCryptoCtx *ctx, uint8_t *in, size_t in_len,
 		uint8_t *out)
 {
-	furi_hal_random_fill_buf(out, IV_BYTES);
+	struct ESubGhzChatCryptoMsg *msg = (struct ESubGhzChatCryptoMsg *) out;
 
+	// fill message header
+	msg->run_id = ctx->run_id;
+	msg->counter = __htonl(ctx->counter);
+	furi_hal_random_fill_buf(msg->iv, IV_BYTES);
+
+	// encrypt message and store tag in header
 #ifdef FURI_HAL_CRYPTO_ADVANCED_AVAIL
-	return (furi_hal_crypto_gcm_encrypt_and_tag(ctx->key,
-			out, in, out + IV_BYTES,
+	bool ret = (furi_hal_crypto_gcm_encrypt_and_tag(ctx->key,
+			msg->iv,
+			(uint8_t *) msg, RUN_ID_BYTES + COUNTER_BYTES,
+			in, msg->data,
 			in_len,
-			out + IV_BYTES + in_len) == FuriHalCryptoGCMStateOk);
+			msg->tag) == FuriHalCryptoGCMStateOk);
 #else /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
-	return (gcm_crypt_and_tag(&(ctx->gcm_ctx), ENCRYPT,
-			out, IV_BYTES,
-			NULL, 0,
-			in, out + IV_BYTES, in_len,
-			out + IV_BYTES + in_len, TAG_BYTES) == 0);
+	bool ret = (gcm_crypt_and_tag(&(ctx->gcm_ctx), ENCRYPT,
+			msg->iv, IV_BYTES,
+			(uint8_t *) msg, RUN_ID_BYTES + COUNTER_BYTES,
+			in, msg->data,
+			in_len,
+			msg->tag, TAG_BYTES) == 0);
 #endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */
+
+	// increase internal counter
+	if (ret) {
+		ctx->counter++;
+	}
+
+	return ret;
 }

+ 5 - 2
crypto_wrapper.h

@@ -4,11 +4,13 @@
 extern "C" {
 #endif
 
+#define RUN_ID_BYTES (sizeof(uint64_t))
+#define COUNTER_BYTES (sizeof(uint32_t))
 #define KEY_BITS 256
 #define IV_BYTES 12
 #define TAG_BYTES 16
 
-#define MSG_OVERHEAD (IV_BYTES + TAG_BYTES)
+#define MSG_OVERHEAD (RUN_ID_BYTES + COUNTER_BYTES + IV_BYTES + TAG_BYTES)
 
 typedef struct ESugGhzChatCryptoCtx ESubGhzChatCryptoCtx;
 
@@ -22,7 +24,8 @@ void crypto_ctx_free(ESubGhzChatCryptoCtx *ctx);
 
 void crypto_ctx_clear(ESubGhzChatCryptoCtx *ctx);
 
-bool crypto_ctx_set_key(ESubGhzChatCryptoCtx *ctx, const uint8_t *key);
+bool crypto_ctx_set_key(ESubGhzChatCryptoCtx *ctx, const uint8_t *key,
+		FuriString *flipper_name, uint32_t tick);
 void crypto_ctx_get_key(ESubGhzChatCryptoCtx *ctx, uint8_t *key);
 
 bool crypto_ctx_decrypt(ESubGhzChatCryptoCtx *ctx, uint8_t *in, size_t in_len,

+ 33 - 10
esubghz_chat.c

@@ -23,6 +23,36 @@ static void have_read_cb(void* context)
 	state->last_time_rx_data = furi_get_tick();
 }
 
+/* Sets the header for the chat input field depending on whether or not a
+ * message preview exists. */
+void set_chat_input_header(ESubGhzChatState *state)
+{
+	if (strlen(state->msg_preview) == 0) {
+		text_input_set_header_text(state->text_input, "Message");
+	} else {
+		text_input_set_header_text(state->text_input,
+				state->msg_preview);
+	}
+}
+
+/* Appends the latest message to the chat box and prepares the message preview.
+ */
+void append_msg(ESubGhzChatState *state, const char *msg)
+{
+	/* append message to text box */
+	furi_string_cat_printf(state->chat_box_store, "\n%s", msg);
+
+	/* prepare message preview */
+	strncpy(state->msg_preview, msg, MSG_PREVIEW_SIZE);
+	state->msg_preview[MSG_PREVIEW_SIZE] = 0;
+	set_chat_input_header(state);
+
+	/* reset text box contents and focus */
+	text_box_set_text(state->chat_box,
+			furi_string_get_cstr(state->chat_box_store));
+	text_box_set_focus(state->chat_box, TextBoxFocusEnd);
+}
+
 /* Decrypts a message for post_rx(). */
 static bool post_rx_decrypt(ESubGhzChatState *state, size_t rx_size)
 {
@@ -39,8 +69,7 @@ static bool post_rx_decrypt(ESubGhzChatState *state, size_t rx_size)
 	return ret;
 }
 
-/* Post RX handler, decrypts received messages, displays them in the text box
- * and sends a notification. */
+/* Post RX handler, decrypts received messages and calls append_msg(). */
 static void post_rx(ESubGhzChatState *state, size_t rx_size)
 {
 	furi_assert(state);
@@ -68,17 +97,11 @@ static void post_rx(ESubGhzChatState *state, size_t rx_size)
 		}
 	}
 
-	/* append message to text box */
-	furi_string_cat_printf(state->chat_box_store, "\n%s",
-			state->rx_str_buffer);
+	/* append message to text box and prepare message preview */
+	append_msg(state, state->rx_str_buffer);
 
 	/* send notification (make the flipper vibrate) */
 	notification_message(state->notification, &sequence_single_vibro);
-
-	/* reset text box contents and focus */
-	text_box_set_text(state->chat_box,
-			furi_string_get_cstr(state->chat_box_store));
-	text_box_set_focus(state->chat_box, TextBoxFocusEnd);
 }
 
 /* Reads the message from msg_input, encrypts it if necessary and then

+ 10 - 0
esubghz_chat_i.h

@@ -24,10 +24,13 @@
 
 #define DEFAULT_FREQ 433920000
 
+#define KEY_READ_POPUP_MS 3000
+
 #define RX_TX_BUFFER_SIZE 1024
 
 #define CHAT_BOX_STORE_SIZE 4096
 #define TEXT_INPUT_STORE_SIZE 256
+#define MSG_PREVIEW_SIZE 32
 
 #define KEY_HEX_STR_SIZE ((KEY_BITS / 8) * 3)
 
@@ -61,6 +64,9 @@ typedef struct {
 	FuriString *name_prefix;
 	FuriString *msg_input;
 
+	// message preview
+	char msg_preview[MSG_PREVIEW_SIZE + 1];
+
 	// encryption
 	bool encrypted;
 	ESubGhzChatCryptoCtx *crypto_ctx;
@@ -91,6 +97,8 @@ typedef enum {
 	ESubGhzChatEvent_KeyMenuHexKey,
 	ESubGhzChatEvent_KeyMenuGenKey,
 	ESubGhzChatEvent_KeyMenuReadKeyFromNfc,
+	ESubGhzChatEvent_KeyReadPopupFailed,
+	ESubGhzChatEvent_KeyReadPopupSucceeded,
 	ESubGhzChatEvent_PassEntered,
 	ESubGhzChatEvent_HexKeyEntered,
 	ESubGhzChatEvent_MsgEntered,
@@ -109,5 +117,7 @@ typedef enum {
 	ESubGhzChatView_NfcPopup,
 } ESubGhzChatView;
 
+void set_chat_input_header(ESubGhzChatState *state);
+void append_msg(ESubGhzChatState *state, const char *msg);
 void tx_msg_input(ESubGhzChatState *state);
 void enter_chat(ESubGhzChatState *state);

+ 20 - 25
scenes/esubghz_chat_chat_input.c

@@ -6,30 +6,28 @@
  * then copied into the TX buffer. The contents of the TX buffer are then
  * transmitted. The sent message is appended to the text box and a MsgEntered
  * event is sent to the scene manager to switch to the text box view. */
-static void chat_input_cb(void *context)
+static bool chat_input_validator(const char *text, FuriString *error,
+		void *context)
 {
+	UNUSED(error);
+
 	furi_assert(context);
 	ESubGhzChatState* state = context;
 
 	/* no message, just switch to the text box view */
-#ifdef FW_ORIGIN_Official
-	if (strcmp(state->text_input_store, " ") == 0) {
-#else /* FW_ORIGIN_Official */
-	if (strlen(state->text_input_store) == 0) {
-#endif /* FW_ORIGIN_Official */
-		scene_manager_handle_custom_event(state->scene_manager,
+	if (strlen(text) == 0) {
+		view_dispatcher_send_custom_event(state->view_dispatcher,
 				ESubGhzChatEvent_MsgEntered);
-		return;
+		return true;
 	}
 
 	/* concatenate the name prefix and the actual message */
 	furi_string_set(state->msg_input, state->name_prefix);
 	furi_string_cat_str(state->msg_input, ": ");
-	furi_string_cat_str(state->msg_input, state->text_input_store);
+	furi_string_cat_str(state->msg_input, text);
 
-	/* append the message to the chat box */
-	furi_string_cat_printf(state->chat_box_store, "\n%s",
-		furi_string_get_cstr(state->msg_input));
+	/* append the message to the chat box and prepare message preview */
+	append_msg(state, furi_string_get_cstr(state->msg_input));
 
 	/* encrypt and transmit message */
 	tx_msg_input(state);
@@ -38,8 +36,10 @@ static void chat_input_cb(void *context)
 	furi_string_set_char(state->msg_input, 0, 0);
 
 	/* switch to text box view */
-	scene_manager_handle_custom_event(state->scene_manager,
+	view_dispatcher_send_custom_event(state->view_dispatcher,
 			ESubGhzChatEvent_MsgEntered);
+
+	return true;
 }
 
 /* Prepares the message input scene. */
@@ -52,25 +52,20 @@ void scene_on_enter_chat_input(void* context)
 
 	state->text_input_store[0] = 0;
 	text_input_reset(state->text_input);
+	/* use validator for scene change to get around minimum length
+	 * requirement */
 	text_input_set_result_callback(
 			state->text_input,
-			chat_input_cb,
-			state,
+			NULL,
+			NULL,
 			state->text_input_store,
 			sizeof(state->text_input_store),
 			true);
 	text_input_set_validator(
 			state->text_input,
-			NULL,
-			NULL);
-	text_input_set_header_text(
-			state->text_input,
-#ifdef FW_ORIGIN_Official
-			"Message (space for none)");
-#else /* FW_ORIGIN_Official */
-			"Message");
-	text_input_set_minimum_length(state->text_input, 0);
-#endif /* FW_ORIGIN_Official */
+			chat_input_validator,
+			state);
+	set_chat_input_header(state);
 
 	view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input);
 }

+ 1 - 1
scenes/esubghz_chat_freq_input.c

@@ -10,7 +10,7 @@ static void freq_input_cb(void *context)
 	furi_string_cat_printf(state->chat_box_store, "Frequency: %lu",
 			state->frequency);
 
-	scene_manager_handle_custom_event(state->scene_manager,
+	view_dispatcher_send_custom_event(state->view_dispatcher,
 			ESubGhzChatEvent_FreqEntered);
 }
 

+ 3 - 2
scenes/esubghz_chat_hex_key_input.c

@@ -9,7 +9,8 @@ static void hex_key_input_cb(void* context)
 
 	/* initiate the crypto context */
 	bool ret = crypto_ctx_set_key(state->crypto_ctx,
-			state->hex_key_input_store);
+			state->hex_key_input_store, state->name_prefix,
+			furi_get_tick());
 
 	/* cleanup */
 	crypto_explicit_bzero(state->hex_key_input_store,
@@ -24,7 +25,7 @@ static void hex_key_input_cb(void* context)
 
 	enter_chat(state);
 
-	scene_manager_handle_custom_event(state->scene_manager,
+	view_dispatcher_send_custom_event(state->view_dispatcher,
 			ESubGhzChatEvent_HexKeyEntered);
 }
 

+ 2 - 2
scenes/esubghz_chat_key_display.c

@@ -7,13 +7,13 @@ void key_display_result_cb(DialogExResult result, void* context)
 
 	switch(result) {
 	case DialogExResultLeft:
-		scene_manager_handle_custom_event(state->scene_manager,
+		view_dispatcher_send_custom_event(state->view_dispatcher,
 				ESubGhzChatEvent_KeyDisplayBack);
 		break;
 
 	case DialogExResultCenter:
 		if (state->encrypted) {
-			scene_manager_handle_custom_event(state->scene_manager,
+			view_dispatcher_send_custom_event(state->view_dispatcher,
 					ESubGhzChatEvent_KeyDisplayShare);
 		}
 		break;

+ 7 - 6
scenes/esubghz_chat_key_menu.c

@@ -20,17 +20,17 @@ static void key_menu_cb(void* context, uint32_t index)
 		state->encrypted = false;
 		enter_chat(state);
 
-		scene_manager_handle_custom_event(state->scene_manager,
+		view_dispatcher_send_custom_event(state->view_dispatcher,
 				ESubGhzChatEvent_KeyMenuNoEncryption);
 		break;
 
 	case ESubGhzChatKeyMenuItems_Password:
-		scene_manager_handle_custom_event(state->scene_manager,
+		view_dispatcher_send_custom_event(state->view_dispatcher,
 				ESubGhzChatEvent_KeyMenuPassword);
 		break;
 
 	case ESubGhzChatKeyMenuItems_HexKey:
-		scene_manager_handle_custom_event(state->scene_manager,
+		view_dispatcher_send_custom_event(state->view_dispatcher,
 				ESubGhzChatEvent_KeyMenuHexKey);
 		break;
 
@@ -39,7 +39,8 @@ static void key_menu_cb(void* context, uint32_t index)
 		furi_hal_random_fill_buf(key, KEY_BITS / 8);
 
 		/* initiate the crypto context */
-		bool ret = crypto_ctx_set_key(state->crypto_ctx, key);
+		bool ret = crypto_ctx_set_key(state->crypto_ctx, key,
+				state->name_prefix, furi_get_tick());
 
 		/* cleanup */
 		crypto_explicit_bzero(key, sizeof(key));
@@ -53,12 +54,12 @@ static void key_menu_cb(void* context, uint32_t index)
 		state->encrypted = true;
 		enter_chat(state);
 
-		scene_manager_handle_custom_event(state->scene_manager,
+		view_dispatcher_send_custom_event(state->view_dispatcher,
 				ESubGhzChatEvent_KeyMenuGenKey);
 		break;
 
 	case ESubGhzChatKeyMenuItems_ReadKeyFromNfc:
-		scene_manager_handle_custom_event(state->scene_manager,
+		view_dispatcher_send_custom_event(state->view_dispatcher,
 				ESubGhzChatEvent_KeyMenuReadKeyFromNfc);
 		break;
 

+ 92 - 22
scenes/esubghz_chat_key_read_popup.c

@@ -4,6 +4,8 @@ typedef enum {
 	KeyReadPopupState_Idle,
 	KeyReadPopupState_Detecting,
 	KeyReadPopupState_Reading,
+	KeyReadPopupState_Fail,
+	KeyReadPopupState_Success,
 } KeyReadPopupState;
 
 static bool read_worker_cb(NfcWorkerEvent event, void* context)
@@ -16,6 +18,26 @@ static bool read_worker_cb(NfcWorkerEvent event, void* context)
 	return true;
 }
 
+static void key_read_popup_timeout_cb(void* context)
+{
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	uint32_t cur_state = scene_manager_get_scene_state(
+			state->scene_manager, ESubGhzChatScene_KeyReadPopup);
+
+	/* done displaying our failure */
+	if (cur_state == KeyReadPopupState_Fail) {
+		view_dispatcher_send_custom_event(state->view_dispatcher,
+				ESubGhzChatEvent_KeyReadPopupFailed);
+	/* done displaying our success, enter chat */
+	} else if (cur_state == KeyReadPopupState_Success) {
+		enter_chat(state);
+		view_dispatcher_send_custom_event(state->view_dispatcher,
+				ESubGhzChatEvent_KeyReadPopupSucceeded);
+	}
+}
+
 static bool key_read_popup_handle_key_read(ESubGhzChatState *state)
 {
 	NfcDeviceData *dev_data = state->nfc_dev_data;
@@ -26,7 +48,8 @@ static bool key_read_popup_handle_key_read(ESubGhzChatState *state)
 
 	/* initiate the crypto context */
 	bool ret = crypto_ctx_set_key(state->crypto_ctx,
-			dev_data->mf_ul_data.data);
+			dev_data->mf_ul_data.data, state->name_prefix,
+			furi_get_tick());
 
 	/* cleanup */
 	crypto_explicit_bzero(dev_data->mf_ul_data.data, KEY_BITS / 8);
@@ -36,9 +59,8 @@ static bool key_read_popup_handle_key_read(ESubGhzChatState *state)
 		return false;
 	}
 
-	/* set encrypted flag and enter the chat */
+	/* set encrypted flag */
 	state->encrypted = true;
-	enter_chat(state);
 
 	return true;
 }
@@ -54,18 +76,62 @@ static void key_read_popup_set_state(ESubGhzChatState *state, KeyReadPopupState
 
 	if (new_state == KeyReadPopupState_Detecting) {
 		popup_reset(state->nfc_popup);
-		popup_set_text(state->nfc_popup, "Apply card to\nFlipper's "
-				"back", 97, 24, AlignCenter, AlignTop);
+		popup_disable_timeout(state->nfc_popup);
+		popup_set_text(state->nfc_popup, "Tap Flipper\n to sender", 97,
+				24, AlignCenter, AlignTop);
 		popup_set_icon(state->nfc_popup, 0, 8, &I_NFC_manual_60x50);
+		notification_message(state->notification,
+				&sequence_blink_start_cyan);
 	} else if (new_state == KeyReadPopupState_Reading) {
 		popup_reset(state->nfc_popup);
-		popup_set_header(state->nfc_popup, "Reading card\nDon't "
+		popup_disable_timeout(state->nfc_popup);
+		popup_set_header(state->nfc_popup, "Reading key\nDon't "
 				"move...", 85, 24, AlignCenter, AlignTop);
 		popup_set_icon(state->nfc_popup, 12, 23, &I_Loading_24);
+		notification_message(state->notification,
+				&sequence_blink_start_yellow);
+	} else if (new_state == KeyReadPopupState_Fail) {
+		nfc_worker_stop(state->nfc_worker);
+
+		popup_reset(state->nfc_popup);
+		popup_set_header(state->nfc_popup, "Failure!", 64, 2,
+				AlignCenter, AlignTop);
+		popup_set_text(state->nfc_popup, "Failed\nto read\nkey.", 78,
+				16, AlignLeft, AlignTop);
+		popup_set_icon(state->nfc_popup, 21, 13, &I_Cry_dolph_55x52);
+
+		popup_set_timeout(state->nfc_popup, KEY_READ_POPUP_MS);
+		popup_set_context(state->nfc_popup, state);
+		popup_set_callback(state->nfc_popup,
+				key_read_popup_timeout_cb);
+		popup_enable_timeout(state->nfc_popup);
+
+		notification_message(state->notification,
+				&sequence_blink_stop);
+	} else if (new_state == KeyReadPopupState_Success) {
+		nfc_worker_stop(state->nfc_worker);
+
+		popup_reset(state->nfc_popup);
+		popup_set_header(state->nfc_popup, "Key\nread!", 13, 22,
+				AlignLeft, AlignBottom);
+		popup_set_icon(state->nfc_popup, 32, 5, &I_DolphinNice_96x59);
+
+		popup_set_timeout(state->nfc_popup, KEY_READ_POPUP_MS);
+		popup_set_context(state->nfc_popup, state);
+		popup_set_callback(state->nfc_popup,
+				key_read_popup_timeout_cb);
+		popup_enable_timeout(state->nfc_popup);
+
+		notification_message(state->notification, &sequence_success);
+		notification_message(state->notification,
+				&sequence_blink_stop);
 	}
 
 	scene_manager_set_scene_state(state->scene_manager,
 			ESubGhzChatScene_KeyReadPopup, new_state);
+
+	view_dispatcher_switch_to_view(state->view_dispatcher,
+			ESubGhzChatView_NfcPopup);
 }
 
 /* Prepares the key share read scene. */
@@ -86,11 +152,6 @@ void scene_on_enter_key_read_popup(void* context)
 
 	nfc_worker_start(state->nfc_worker, NfcWorkerStateRead,
 			state->nfc_dev_data, read_worker_cb, state);
-
-	notification_message(state->notification, &sequence_blink_start_cyan);
-
-	view_dispatcher_switch_to_view(state->view_dispatcher,
-			ESubGhzChatView_NfcPopup);
 }
 
 /* Handles scene manager events for the key read popup scene. */
@@ -110,8 +171,6 @@ bool scene_on_event_key_read_popup(void* context, SceneManagerEvent event)
 		case NfcWorkerEventCardDetected:
 			key_read_popup_set_state(state,
 					KeyReadPopupState_Reading);
-			notification_message(state->notification,
-					&sequence_blink_start_yellow);
 			consumed = true;
 			break;
 
@@ -119,32 +178,43 @@ bool scene_on_event_key_read_popup(void* context, SceneManagerEvent event)
 		case NfcWorkerEventNoCardDetected:
 			key_read_popup_set_state(state,
 					KeyReadPopupState_Detecting);
-			notification_message(state->notification,
-					&sequence_blink_start_cyan);
 			consumed = true;
 			break;
 
 		/* key probably read */
 		case NfcWorkerEventReadMfUltralight:
 			if (key_read_popup_handle_key_read(state)) {
-				scene_manager_next_scene(state->scene_manager,
-						ESubGhzChatScene_ChatInput);
+				key_read_popup_set_state(state,
+						KeyReadPopupState_Success);
 			} else {
-				if (!scene_manager_previous_scene(
-							state->scene_manager)) {
-					view_dispatcher_stop(state->view_dispatcher);
-				}
+				key_read_popup_set_state(state,
+						KeyReadPopupState_Fail);
 			}
 			consumed = true;
 			break;
 
-		default:
+		/* close the popup and go back */
+		case ESubGhzChatEvent_KeyReadPopupFailed:
 			if (!scene_manager_previous_scene(
 						state->scene_manager)) {
 				view_dispatcher_stop(state->view_dispatcher);
 			}
 			consumed = true;
 			break;
+
+		/* success, go to chat input */
+		case ESubGhzChatEvent_KeyReadPopupSucceeded:
+			scene_manager_next_scene(state->scene_manager,
+					ESubGhzChatScene_ChatInput);
+			consumed = true;
+			break;
+
+		/* something else happend, treat as failure */
+		default:
+			key_read_popup_set_state(state,
+					KeyReadPopupState_Fail);
+			consumed = true;
+			break;
 		}
 
 		break;

+ 3 - 2
scenes/esubghz_chat_pass_input.c

@@ -11,7 +11,7 @@ static void pass_input_cb(void *context)
 
 	enter_chat(state);
 
-	scene_manager_handle_custom_event(state->scene_manager,
+	view_dispatcher_send_custom_event(state->view_dispatcher,
 			ESubGhzChatEvent_PassEntered);
 }
 
@@ -38,7 +38,8 @@ static bool pass_input_validator(const char *text, FuriString *error,
 	sha256((unsigned char *) text, strlen(text), key);
 
 	/* initiate the crypto context */
-	bool ret = crypto_ctx_set_key(state->crypto_ctx, key);
+	bool ret = crypto_ctx_set_key(state->crypto_ctx, key,
+			state->name_prefix, furi_get_tick());
 
 	/* cleanup */
 	crypto_explicit_bzero(key, sizeof(key));