otp: prefill otp keyboard with issuer name after scanning qr
What changed, and why it matters
This commit is a user-experience improvement for setting up one-time password (OTP) accounts on the Jade hardware wallet. After scanning a QR code, the device now automatically fills the 'OTP Name' field with the website/service name (issuer) from the QR code, instead of making the user type it from scratch. It also adds a helper to clean up that name so it can be safely used as a storage key. There is no apparent security fix or vulnerability here.
No security action required. Treat as routine UX/feature commit. If auditing, verify that urldecode and storage_key_name_make_valid handle edge cases (empty issuer, over-long strings, null bytes) safely, but the change appears defensive rather than remedial.
Security signals we found
No security-relevant signals detected in the diff or commit message.
New helper ensures storage key names conform to existing validity rules, which is defensive but not a response to a disclosed vulnerability.
Buffers are zero-initialized, a good practice but not evidence of a prior bug being fixed.
Evidence from the diff
The change modifies OTP registration flow. In register_otp_string(), when an issuer is present in the parsed OTP URI, it URL-decodes the issuer into otp_name and calls a new storage_key_name_make_valid() helper to replace non-printable characters with underscores and truncate to NVS key length. The keyboard activity is then pre-populated with this name. A new helper storage_key_name_make_valid() is added in storage.c/storage.h. The buffers otp_name and otp_uri are also zero-initialized. The diff shows only feature/UX hardening, not a security patch.
Changed components
main/process/register_otp.cmain/storage.cmain/storage.hInspect captured patch +43 / −4
diff --git a/main/process/register_otp.c b/main/process/register_otp.c
index 74cea65..eca0165 100644
--- a/main/process/register_otp.c
+++ b/main/process/register_otp.c
@@ -7,6 +7,7 @@
#include "../storage.h"
#include "../ui.h"
#include "../utils/cbor_rpc.h"
+#include "../utils/urldecode.h"
#include "../button_events.h"
@@ -170,6 +171,16 @@ static bool get_otp_data_from_kb(
make_keyboard_entry_activity(&kb_entry, "OTP Name");
JADE_ASSERT(kb_entry.activity);
+ // if otp_name is provided, pre-enter it into the kb activity
+ if (otp_name[0]) {
+ JADE_ASSERT(name_len <= sizeof(kb_entry.strdata));
+ const size_t max_len = strnlen(otp_name, sizeof(kb_entry.strdata) - 1);
+ strncpy(kb_entry.strdata, otp_name, max_len);
+ kb_entry.strdata[max_len] = '\0';
+ kb_entry.len = max_len;
+ JADE_LOGI("Pre-filled OTP name in keyboard activity: %d, %s", (int)kb_entry.len, kb_entry.strdata);
+ }
+
// 1. Get the OTP Name from the keyboard
bool done = false;
while (!done) {
@@ -267,8 +278,8 @@ bool register_otp_kb_entry(void)
const char* errmsg = NULL;
// Get OTP Name and URI from kb
- char otp_name[OTP_MAX_NAME_LEN];
- char otp_uri[OTP_MAX_URI_LEN];
+ char otp_name[OTP_MAX_NAME_LEN] = { 0 };
+ char otp_uri[OTP_MAX_URI_LEN] = { 0 };
SENSITIVE_PUSH(otp_uri, sizeof(otp_uri));
size_t uri_written = 0;
@@ -354,8 +365,14 @@ int register_otp_string(const char* otp_uri, const size_t uri_len, const char**
return CBOR_RPC_INTERNAL_ERROR;
}
- // Get OTP Name (only) from kb
- char otp_name[OTP_MAX_NAME_LEN];
+ // Get OTP Name (only) from kb (if we have an issuer, prefill that)
+ char otp_name[OTP_MAX_NAME_LEN] = { 0 };
+ if (otp_ctx.issuer_len) {
+ // If have issuer, prefill otp_name with urldecoded version (truncates to fit if too long)
+ urldecode(otp_ctx.issuer, otp_ctx.issuer_len, otp_name, sizeof(otp_name));
+ // Ensure prefilled name is valid to use as storage key (eg. strip out any invalid chars)
+ storage_key_name_make_valid(otp_name);
+ }
if (!get_otp_data_from_kb(otp_name, sizeof(otp_name), NULL, 0, NULL)) {
// User abandoned
JADE_LOGW("User abandoned (entering otp name)");
diff --git a/main/storage.c b/main/storage.c
index ae22af0..c8e1876 100644
--- a/main/storage.c
+++ b/main/storage.c
@@ -412,6 +412,27 @@ bool storage_key_name_valid(const char* name)
return pch > name;
}
+void storage_key_name_make_valid(char* name)
+{
+ // Make name conform to the same rules as storage_key_name_valid()
+ // by replacing any non-conforming characters with '_'.
+ // Truncate if name is too long.
+ // Assert name is not an empty string.
+ char* pch = name;
+ while (*pch != '\0') {
+ if ((pch - name) >= NVS_KEY_NAME_MAX_SIZE) {
+ *pch = '\0';
+ return;
+ }
+ const unsigned char c = *pch;
+ if (!isgraph(c)) {
+ *pch = '_';
+ }
+ ++pch;
+ }
+ JADE_ASSERT(pch > name);
+}
+
bool storage_get_pin_privatekey(uint8_t* privatekey, const size_t key_len)
{
JADE_ASSERT(privatekey);
diff --git a/main/storage.h b/main/storage.h
index 2e3fbdc..8d8adee 100644
--- a/main/storage.h
+++ b/main/storage.h
@@ -38,6 +38,7 @@ bool storage_init(void);
bool storage_erase(void);
bool storage_get_stats(size_t* entries_used, size_t* entries_free);
bool storage_key_name_valid(const char* name);
+void storage_key_name_make_valid(char* name);
bool storage_set_pin_privatekey(const uint8_t* privatekey, size_t key_len);
bool storage_get_pin_privatekey(uint8_t* privatekey, size_t key_len);
Why this scored 12/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.