otp: added intermediate options screen for otp details
What changed, and why it matters
This commit adds a new intermediate menu screen for one-time password (OTP) entries in the Blockstream Jade hardware wallet. When a user views an OTP, they now see a menu with 'View' and 'Export' options. The 'Export' button currently just shows a placeholder message saying the feature is not implemented yet. There is no security-relevant change in this commit.
No security action required. This is a UI/UX feature commit. If 'Export' is later implemented, that future change should be reviewed for secure handling of OTP secrets.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change introduces make_view_export_otp_activity() in main/ui/otpauth.c, which creates a two-button menu (‘View’ and ‘Export’) for OTP records. It adds two new button event IDs in main/button_events.h and wires the new options screen into the HOTP/TOTP display flows and the OTP list view in main/process/dashboard.c. Selecting ‘View’ proceeds to the existing OTP details screen; selecting ‘Export’ displays a placeholder message. No cryptographic, authentication, access-control, or data-handling logic is modified.
Changed components
main/ui/otpauth.cmain/process/dashboard.cmain/button_events.hInspect captured patch +44 / −3
diff --git a/main/button_events.h b/main/button_events.h
index 396dbbc..6e82892 100644
--- a/main/button_events.h
+++ b/main/button_events.h
@@ -247,6 +247,8 @@ typedef enum {
BTN_OTP_ISSUER,
BTN_OTP_TYPE,
BTN_OTP_DETAILS,
+ BTN_OTP_DETAILS_VIEW,
+ BTN_OTP_DETAILS_EXPORT,
BTN_OTP_RETAIN_CONFIRM,
BTN_OTP_DISCARD_DELETE,
diff --git a/main/process/dashboard.c b/main/process/dashboard.c
index 292763a..d5614ac 100644
--- a/main/process/dashboard.c
+++ b/main/process/dashboard.c
@@ -202,6 +202,8 @@ gui_activity_t* make_bip39_passphrase_prefs_activity(
gui_activity_t* make_otp_activity(void);
gui_activity_t* make_new_otp_activity(void);
+gui_activity_t* make_view_export_otp_activity(const char* name);
+
bool show_otp_details_activity(
const otpauth_ctx_t* ctx, bool initial_confirmation, bool is_valid, bool show_delete_btn);
gui_activity_t* make_show_hotp_code_activity(const char* name, const char* codestr, bool confirm_only);
@@ -1432,6 +1434,30 @@ static bool delete_otp_record(const char* otpname)
return true;
}
+static bool show_otp_detail_options_activity(
+ const otpauth_ctx_t* otp_ctx, const bool initial_confirmation, const bool is_valid, const bool show_delete_btn)
+{
+ JADE_ASSERT(otp_ctx);
+ JADE_ASSERT(otp_ctx->name);
+
+ gui_activity_t* const act = make_view_export_otp_activity(otp_ctx->name);
+ int32_t ev_id;
+
+ while (true) {
+ gui_set_current_activity(act);
+
+ if (gui_activity_wait_event(act, GUI_BUTTON_EVENT, ESP_EVENT_ANY_ID, NULL, &ev_id, NULL, 0)) {
+ if (ev_id == BTN_BACK) {
+ return true;
+ } else if (ev_id == BTN_OTP_DETAILS_VIEW) {
+ return show_otp_details_activity(otp_ctx, initial_confirmation, is_valid, show_delete_btn);
+ } else if (ev_id == BTN_OTP_DETAILS_EXPORT) {
+ const char* msg[] = { "Export is not", "implemented yet" };
+ await_message_activity(msg, 2);
+ }
+ }
+ }
+}
// HOTP token-code fixed
static bool display_hotp_screen(const otpauth_ctx_t* otp_ctx, const char* token, const bool confirm_only)
{
@@ -1448,7 +1474,8 @@ static bool display_hotp_screen(const otpauth_ctx_t* otp_ctx, const char* token,
const bool is_valid = true; // asserted above
const bool initial_confirmation = false;
const bool show_delete_btn = false;
- const bool retain = show_otp_details_activity(otp_ctx, initial_confirmation, is_valid, show_delete_btn);
+ const bool retain
+ = show_otp_detail_options_activity(otp_ctx, initial_confirmation, is_valid, show_delete_btn);
JADE_ASSERT(retain); // should be no 'discard' option
} else if (ev_id == BTN_OTP_DISCARD_DELETE) {
if (confirm_only || delete_otp_record(otp_ctx->name))
@@ -1547,7 +1574,8 @@ static bool display_totp_screen(otpauth_ctx_t* otp_ctx, uint64_t epoch_value, ch
const bool is_valid = true; // asserted above
const bool initial_confirmation = false;
const bool show_delete_btn = false;
- const bool retain = show_otp_details_activity(otp_ctx, initial_confirmation, is_valid, show_delete_btn);
+ const bool retain
+ = show_otp_detail_options_activity(otp_ctx, initial_confirmation, is_valid, show_delete_btn);
JADE_ASSERT(retain); // should be no 'discard' option
} else if (ev_id == BTN_OTP_DISCARD_DELETE) {
if (confirm_only || delete_otp_record(otp_ctx->name))
@@ -1677,7 +1705,7 @@ static void handle_view_otps(void)
JADE_LOGE("Error loading or executing otp record: %s", names[selected]);
const bool initial_confirmation = false;
const bool show_delete_btn = true;
- if (!show_otp_details_activity(&otp_ctx, initial_confirmation, is_valid, show_delete_btn)) {
+ if (!show_otp_detail_options_activity(&otp_ctx, initial_confirmation, is_valid, show_delete_btn)) {
// Delete invalid record
delete_otp_record(otp_ctx.name);
}
diff --git a/main/ui/otpauth.c b/main/ui/otpauth.c
index 06db949..fe2aa65 100644
--- a/main/ui/otpauth.c
+++ b/main/ui/otpauth.c
@@ -164,6 +164,17 @@ static gui_activity_t* make_otp_details_activities(const otpauth_ctx_t* ctx, con
return act;
}
+gui_activity_t* make_view_export_otp_activity(const char* name)
+{
+ btn_data_t hdrbtns[] = { { .txt = "=", .font = JADE_SYMBOLS_16x16_FONT, .ev_id = BTN_BACK },
+ { .txt = NULL, .font = GUI_DEFAULT_FONT, .ev_id = GUI_BUTTON_EVENT_NONE } };
+
+ btn_data_t menubtns[] = { { .txt = "View", .font = GUI_DEFAULT_FONT, .ev_id = BTN_OTP_DETAILS_VIEW },
+ { .txt = "Export", .font = GUI_DEFAULT_FONT, .ev_id = BTN_OTP_DETAILS_EXPORT } };
+
+ return make_menu_activity(name, hdrbtns, 2, menubtns, 2);
+}
+
// otp details screen for viewing or confirmation
// returns true if we are to store/retain this record, false if we are to discard/delete the record
bool show_otp_details_activity(
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.