diff --git a/src_features/signMessageEIP712/commands_712.c b/src_features/signMessageEIP712/commands_712.c index 98661cf48b..41052e7d8f 100644 --- a/src_features/signMessageEIP712/commands_712.c +++ b/src_features/signMessageEIP712/commands_712.c @@ -28,6 +28,7 @@ #define P2_IMPL_FIELD P2_DEF_FIELD #define P2_FILT_ACTIVATE 0x00 #define P2_FILT_MESSAGE_INFO 0x0F +#define P2_FILT_DATE_TIME 0xFC #define P2_FILT_AMOUNT_JOIN_TOKEN 0xFD #define P2_FILT_AMOUNT_JOIN_VALUE 0xFE #define P2_FILT_RAW_FIELD 0xFF @@ -177,6 +178,9 @@ bool handle_eip712_filtering(const uint8_t *const apdu_buf) { reply_apdu = false; } break; + case P2_FILT_DATE_TIME: + ret = filtering_date_time(&apdu_buf[OFFSET_CDATA], apdu_buf[OFFSET_LC]); + break; case P2_FILT_AMOUNT_JOIN_TOKEN: ret = filtering_amount_join_token(&apdu_buf[OFFSET_CDATA], apdu_buf[OFFSET_LC]); break; diff --git a/src_features/signMessageEIP712/filtering.c b/src_features/signMessageEIP712/filtering.c index f70292aeb1..4ca19d38db 100644 --- a/src_features/signMessageEIP712/filtering.c +++ b/src_features/signMessageEIP712/filtering.c @@ -14,6 +14,7 @@ #define FILT_MAGIC_MESSAGE_INFO 183 #define FILT_MAGIC_AMOUNT_JOIN_TOKEN 11 #define FILT_MAGIC_AMOUNT_JOIN_VALUE 22 +#define FILT_MAGIC_DATETIME 33 #define FILT_MAGIC_RAW_FIELD 72 /** @@ -214,6 +215,66 @@ bool filtering_message_info(const uint8_t *payload, uint8_t length) { return true; } +/** + * Command to display a field as a date-time + * + * @param[in] payload the payload to parse + * @param[in] length the payload length + * @return whether it was successful or not + */ +bool filtering_date_time(const uint8_t *payload, uint8_t length) { + uint8_t name_len; + const char *name; + uint8_t sig_len; + const uint8_t *sig; + uint8_t offset = 0; + + if (path_get_root_type() != ROOT_MESSAGE) { + apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED; + return false; + } + + // Parsing + if ((offset + sizeof(name_len)) > length) { + return false; + } + name_len = payload[offset++]; + if ((offset + name_len) > length) { + return false; + } + name = (char *) &payload[offset]; + offset += name_len; + if ((offset + sizeof(sig_len)) > length) { + return false; + } + sig_len = payload[offset++]; + if ((offset + sig_len) != length) { + return false; + } + sig = &payload[offset]; + + // Verification + cx_sha256_t hash_ctx; + if (!sig_verif_start(&hash_ctx, FILT_MAGIC_DATETIME)) { + return false; + } + hash_filtering_path((cx_hash_t *) &hash_ctx); + hash_nbytes((uint8_t *) name, sizeof(char) * name_len, (cx_hash_t *) &hash_ctx); + if (!sig_verif_end(&hash_ctx, sig, sig_len)) { + return false; + } + + // Handling + if (!check_typename("uint")) { + return false; + } + if (name_len > 0) { // don't substitute for an empty name + ui_712_set_title(name, name_len); + } + ui_712_flag_field(true, name_len > 0, false, true); + return true; +} + /** * Command to display a field as an amount-join (token part) * @@ -261,7 +322,7 @@ bool filtering_amount_join_token(const uint8_t *payload, uint8_t length) { if (!check_typename("address") || !check_token_index(token_idx)) { return false; } - ui_712_flag_field(false, false, true); + ui_712_flag_field(false, false, true, false); ui_712_token_join_prepare_addr_check(token_idx); return true; } @@ -328,7 +389,7 @@ bool filtering_amount_join_value(const uint8_t *payload, uint8_t length) { if (!check_typename("uint") || !check_token_index(token_idx)) { return false; } - ui_712_flag_field(false, false, true); + ui_712_flag_field(false, false, true, false); ui_712_token_join_prepare_amount(token_idx, name, name_len); return true; } @@ -386,7 +447,7 @@ bool filtering_raw_field(const uint8_t *payload, uint8_t length) { if (name_len > 0) { // don't substitute for an empty name ui_712_set_title(name, name_len); } - ui_712_flag_field(true, name_len > 0, false); + ui_712_flag_field(true, name_len > 0, false, false); return true; } diff --git a/src_features/signMessageEIP712/filtering.h b/src_features/signMessageEIP712/filtering.h index 8f0e271b74..50bc3bb3db 100644 --- a/src_features/signMessageEIP712/filtering.h +++ b/src_features/signMessageEIP712/filtering.h @@ -7,6 +7,7 @@ #include bool filtering_message_info(const uint8_t *payload, uint8_t length); +bool filtering_date_time(const uint8_t *payload, uint8_t length); bool filtering_amount_join_token(const uint8_t *payload, uint8_t length); bool filtering_amount_join_value(const uint8_t *payload, uint8_t length); bool filtering_raw_field(const uint8_t *payload, uint8_t length); diff --git a/src_features/signMessageEIP712/ui_logic.c b/src_features/signMessageEIP712/ui_logic.c index c5b3690ab4..274230fed5 100644 --- a/src_features/signMessageEIP712/ui_logic.c +++ b/src_features/signMessageEIP712/ui_logic.c @@ -2,6 +2,7 @@ #include #include +#include #include "ui_logic.h" #include "mem.h" #include "mem_utils.h" @@ -39,6 +40,7 @@ typedef enum { #define UI_712_FIELD_SHOWN (1 << 0) #define UI_712_FIELD_NAME_PROVIDED (1 << 1) #define UI_712_AMOUNT_JOIN (1 << 2) +#define UI_712_DATETIME (1 << 3) typedef struct { s_amount_join joins[MAX_ITEMS]; @@ -426,6 +428,42 @@ static bool update_amount_join(const uint8_t *data, uint8_t length) { return true; } +/** + * Format given data as a human-readable date/time representation + * + * @param[in] data the data that needs formatting + * @param[in] length its length + * @return whether it was successful or not + */ +static bool ui_712_format_datetime(const uint8_t *data, uint8_t length) { + struct tm tstruct; + int shown_hour; + time_t timestamp = u64_from_BE(data, length); + + if (gmtime_r(×tamp, &tstruct) == NULL) { + return false; + } + if (tstruct.tm_hour == 0) { + shown_hour = 12; + } else { + shown_hour = tstruct.tm_hour; + if (shown_hour > 12) { + shown_hour -= 12; + } + } + snprintf(strings.tmp.tmp, + sizeof(strings.tmp.tmp), + "%04d-%02d-%02d\n%02d:%02d:%02d %s UTC", + tstruct.tm_year + 1900, + tstruct.tm_mon + 1, + tstruct.tm_mday, + shown_hour, + tstruct.tm_min, + tstruct.tm_sec, + (tstruct.tm_hour < 12) ? "AM" : "PM"); + return true; +} + /** * Used to notify of a new field to review in the current struct (key + value) * @@ -497,6 +535,12 @@ bool ui_712_new_field(const void *const field_ptr, const uint8_t *const data, ui } } + if (ui_ctx->field_flags & UI_712_DATETIME) { + if (!ui_712_format_datetime(data, length)) { + return false; + } + } + // Check if this field is supposed to be displayed if (ui_712_field_shown()) { ui_712_redraw_generic_step(); @@ -573,8 +617,9 @@ unsigned int ui_712_reject() { * @param[in] show if this field should be shown on the device * @param[in] name_provided if a substitution name has been provided * @param[in] token_join if this field is part of a token join + * @param[in] datetime if this field should be shown and formatted as a date/time */ -void ui_712_flag_field(bool show, bool name_provided, bool token_join) { +void ui_712_flag_field(bool show, bool name_provided, bool token_join, bool datetime) { if (show) { ui_ctx->field_flags |= UI_712_FIELD_SHOWN; } @@ -584,6 +629,9 @@ void ui_712_flag_field(bool show, bool name_provided, bool token_join) { if (token_join) { ui_ctx->field_flags |= UI_712_AMOUNT_JOIN; } + if (datetime) { + ui_ctx->field_flags |= UI_712_DATETIME; + } } /** diff --git a/src_features/signMessageEIP712/ui_logic.h b/src_features/signMessageEIP712/ui_logic.h index 9f0e85b5bf..81c5916596 100644 --- a/src_features/signMessageEIP712/ui_logic.h +++ b/src_features/signMessageEIP712/ui_logic.h @@ -26,7 +26,7 @@ void ui_712_set_title(const char *const str, uint8_t length); void ui_712_set_value(const char *const str, uint8_t length); void ui_712_message_hash(void); void ui_712_redraw_generic_step(void); -void ui_712_flag_field(bool show, bool name_provided, bool token_join); +void ui_712_flag_field(bool show, bool name_provided, bool token_join, bool datetime); void ui_712_field_flags_reset(void); void ui_712_finalize_field(void); void ui_712_set_filtering_mode(e_eip712_filtering_mode mode);