otp: clearer message when device does not have current time for TOTP function
What changed, and why it matters
This commit improves the on-screen error messages shown when a Blockstream Jade hardware wallet cannot generate a TOTP (time-based one-time password) code because the device does not know the current time. It does not change the security rules or fix a vulnerability; it only makes the failure message clearer to the user, suggesting they unlock the device with the Blockstream app to set the time.
No security action required; treat as a normal UX/clarity improvement. If reviewing, verify that all callers of otp_set_default_value() were updated and that the new enum values are handled appropriately.
Security signals we found
No change to access control, authentication, or cryptographic logic
No change to input parsing or trust boundaries
Error return path still prevents OTP generation when time is unset
Commit title and message describe UX improvement, not a security fix
Evidence from the diff
The change refactors otp_set_default_value() from returning a bool to returning a typed otp_err_t enum (OK, TOTP_TIME, HOTP_COUNTER). Callers in dashboard.c and get_otp_code.c now switch on the specific error so the UI can show a tailored message for the missing-time case. The underlying behavior remains the same: if time is unset or below MIN_ALLOWED_CURRENT_TIMESTAMP, the function still fails and no OTP code is produced. get_otp_code.c continues to treat any non-OK result as an internal error. This is a user-experience/clarity improvement, not a security patch.
Changed components
main/otpauth.cmain/otpauth.hmain/process/dashboard.cmain/process/get_otp_code.cInspect captured patch +32 / −12
diff --git a/main/otpauth.c b/main/otpauth.c
index 18fb37e..d2120b3 100644
--- a/main/otpauth.c
+++ b/main/otpauth.c
@@ -217,7 +217,7 @@ void otp_set_explicit_value(otpauth_ctx_t* otp_ctx, const int64_t value)
}
}
-bool otp_set_default_value(otpauth_ctx_t* otp_ctx, uint64_t* value_out)
+otp_err_t otp_set_default_value(otpauth_ctx_t* otp_ctx, uint64_t* value_out)
{
JADE_ASSERT(otp_is_valid(otp_ctx));
@@ -227,14 +227,14 @@ bool otp_set_default_value(otpauth_ctx_t* otp_ctx, uint64_t* value_out)
value = time(NULL);
if (value < MIN_ALLOWED_CURRENT_TIMESTAMP) {
JADE_LOGE("Using TOTP without time set!");
- return false;
+ return OTP_ERR_TOTP_TIME;
}
} else {
// HOTP uses an incrementing counter held in storage
value = storage_get_otp_hotp_counter(otp_ctx->name);
if (!storage_set_otp_hotp_counter(otp_ctx->name, value + 1)) {
JADE_LOGE("Failed to increment HOTP counter!");
- return false;
+ return OTP_ERR_HOTP_COUNTER;
}
}
@@ -243,7 +243,7 @@ bool otp_set_default_value(otpauth_ctx_t* otp_ctx, uint64_t* value_out)
if (value_out) {
*value_out = value;
}
- return true;
+ return OTP_ERR_OK;
}
static inline mbedtls_md_type_t get_md_type(const otpauth_ctx_t* otp_ctx)
diff --git a/main/otpauth.h b/main/otpauth.h
index 3ea4ece..86c0768 100644
--- a/main/otpauth.h
+++ b/main/otpauth.h
@@ -33,6 +33,8 @@ typedef struct otpauth_ctx {
int8_t period;
} otpauth_ctx_t;
+typedef enum { OTP_ERR_OK, OTP_ERR_TOTP_TIME, OTP_ERR_HOTP_COUNTER } otp_err_t;
+
bool otp_is_valid(const otpauth_ctx_t* otp_ctx);
// Parse the otp uri into a context object
@@ -40,7 +42,7 @@ bool otp_uri_to_ctx(const char* uri, size_t uri_len, otpauth_ctx_t* otp_ctx);
// Update the context object with an explicit or default/calculated nonce value
void otp_set_explicit_value(otpauth_ctx_t* otp_ctx, int64_t value);
-bool otp_set_default_value(otpauth_ctx_t* otp_ctx, uint64_t* value_out);
+otp_err_t otp_set_default_value(otpauth_ctx_t* otp_ctx, uint64_t* value_out);
// Get the auth code for the given context
bool otp_get_auth_code(const otpauth_ctx_t* otp_ctx, char* token, size_t token_len);
diff --git a/main/process/dashboard.c b/main/process/dashboard.c
index 7835c45..5b8ee8a 100644
--- a/main/process/dashboard.c
+++ b/main/process/dashboard.c
@@ -1497,11 +1497,20 @@ static bool display_totp_screen(otpauth_ctx_t* otp_ctx, uint64_t epoch_value, ch
// Update values
if (auto_update) {
- if (!otp_set_default_value(otp_ctx, &epoch_value)) {
- const char* message[] = { "Failed to fetch", "time/counter!" };
- await_error_activity(message, 2);
+ switch (otp_set_default_value(otp_ctx, &epoch_value)) {
+ case OTP_ERR_TOTP_TIME: {
+ const char* msg_totp[] = { "Failed to fetch time.", "Unlock with the", "Blockstream app." };
+ await_error_activity(msg_totp, 3);
+ return false;
+ }
+ case OTP_ERR_HOTP_COUNTER: {
+ const char* msg_hotp[] = { "Failed to fetch", "counter!" };
+ await_error_activity(msg_hotp, 2);
return false;
}
+ case OTP_ERR_OK:
+ break;
+ }
ctime_r((time_t*)&epoch_value, timestr);
gui_update_text(txt_ts, timestr);
@@ -1572,11 +1581,20 @@ static bool show_otp_code(otpauth_ctx_t* otp_ctx)
// Update context with current default 'moving' element
uint64_t value = 0;
- if (!otp_set_default_value(otp_ctx, &value)) {
- const char* message[] = { "Failed to fetch", "time/counter!" };
- await_error_activity(message, 2);
+ switch (otp_set_default_value(otp_ctx, &value)) {
+ case OTP_ERR_TOTP_TIME: {
+ const char* msg_totp[] = { "Failed to fetch time.", "Unlock with the", "Blockstream app." };
+ await_error_activity(msg_totp, 3);
+ return false;
+ }
+ case OTP_ERR_HOTP_COUNTER: {
+ const char* msg_hotp[] = { "Failed to fetch", "counter!" };
+ await_error_activity(msg_hotp, 2);
return false;
}
+ case OTP_ERR_OK:
+ break;
+ }
// Calculate token
char token[OTP_MAX_TOKEN_LEN];
diff --git a/main/process/get_otp_code.c b/main/process/get_otp_code.c
index 7968016..1bbae88 100644
--- a/main/process/get_otp_code.c
+++ b/main/process/get_otp_code.c
@@ -63,7 +63,7 @@ void get_otp_code_process(void* process_ptr)
// Update the context with the current calculated counter value (derived from current time for TOTP)
uint64_t value = 0;
- if (!otp_set_default_value(&otp_ctx, &value)) {
+ if (otp_set_default_value(&otp_ctx, &value) != OTP_ERR_OK) {
jade_process_reject_message(process, CBOR_RPC_INTERNAL_ERROR, "Failed to set OTP counter");
goto cleanup;
}
Why this scored 20/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.