#ifdef HAVE_EIP712_FULL_SUPPORT #include #include #include "path.h" #include "mem.h" #include "context_712.h" #include "commands_712.h" #include "type_hash.h" #include "shared_context.h" #include "mem_utils.h" #include "apdu_constants.h" // APDU response codes #include "typed_data.h" static s_path *path_struct = NULL; /** * Get the field pointer to by the first N depths of the path. * * @param[out] fields_count_ptr the number of fields in the last evaluated depth * @param[in] n the number of depths to evaluate * @return the field which the first Nth depths points to */ static const void *get_nth_field(uint8_t *const fields_count_ptr, uint8_t n) { const void *struct_ptr = NULL; const void *field_ptr = NULL; const char *typename; uint8_t length; uint8_t fields_count; if (path_struct == NULL) { return NULL; } struct_ptr = path_struct->root_struct; if (n > path_struct->depth_count) // sanity check { return NULL; } for (uint8_t depth = 0; depth < n; ++depth) { field_ptr = get_struct_fields_array(struct_ptr, &fields_count); if (fields_count_ptr != NULL) { *fields_count_ptr = fields_count; } // check if the index at this depth makes sense if (path_struct->depths[depth] > fields_count) { return NULL; } for (uint8_t index = 0; index < path_struct->depths[depth]; ++index) { field_ptr = get_next_struct_field(field_ptr); } if (struct_field_type(field_ptr) == TYPE_CUSTOM) { typename = get_struct_field_typename(field_ptr, &length); if ((struct_ptr = get_structn(typename, length)) == NULL) { return NULL; } } } return field_ptr; } /** * Get the element the path is pointing to. * * @param[out] the number of fields in the depth of the returned field * @return the field which the path points to */ static inline const void *get_field(uint8_t *const fields_count) { return get_nth_field(fields_count, path_struct->depth_count); } /** * Get Nth struct field from path * * @param[in] n nth depth requested * @return pointer to the matching field, \ref NULL otherwise */ const void *path_get_nth_field(uint8_t n) { return get_nth_field(NULL, n); } /** * Get Nth to last struct field from path * * @param[in] n nth to last depth requested * @return pointer to the matching field, \ref NULL otherwise */ const void *path_get_nth_field_to_last(uint8_t n) { const char *typename; uint8_t typename_len; const void *field_ptr; const void *struct_ptr = NULL; field_ptr = get_nth_field(NULL, path_struct->depth_count - n); if (field_ptr != NULL) { typename = get_struct_field_typename(field_ptr, &typename_len); struct_ptr = get_structn(typename, typename_len); } return struct_ptr; } /** * Get the element the path is pointing to * * @return the field which the path points to */ const void *path_get_field(void) { return get_field(NULL); } /** * Go down (add) a depth level. * * @return whether the push was successful */ static bool path_depth_list_push(void) { if (path_struct == NULL) { return false; } if (path_struct->depth_count == MAX_PATH_DEPTH) { return false; } path_struct->depths[path_struct->depth_count] = 0; path_struct->depth_count += 1; return true; } /** * Get the last hashing context (corresponding to the current path depth) * * @return pointer to the hashing context */ static cx_sha3_t *get_last_hash_ctx(void) { return ((cx_sha3_t *) mem_alloc(0)) - 1; } /** * Finalize the last hashing context * * @param[out] hash pointer to buffer where the hash will be stored * @return whether there was anything hashed at this depth */ static bool finalize_hash_depth(uint8_t *hash) { const cx_sha3_t *hash_ctx; size_t hashed_bytes; cx_err_t error = CX_INTERNAL_ERROR; hash_ctx = get_last_hash_ctx(); hashed_bytes = hash_ctx->blen; // finalize hash CX_CHECK( cx_hash_no_throw((cx_hash_t *) hash_ctx, CX_LAST, NULL, 0, hash, KECCAK256_HASH_BYTESIZE)); mem_dealloc(sizeof(*hash_ctx)); // remove hash context return hashed_bytes > 0; end: return false; } /** * Continue last progressive hashing context with given hash * * @param[in] hash pointer to given hash */ static void feed_last_hash_depth(const uint8_t *const hash) { const cx_sha3_t *hash_ctx; hash_ctx = get_last_hash_ctx(); // continue progressive hash with the array hash CX_ASSERT(cx_hash_no_throw((cx_hash_t *) hash_ctx, 0, hash, KECCAK256_HASH_BYTESIZE, NULL, 0)); } /** * Create a new hashing context depth in memory * * @param[in] init if the hashing context should be initialized * @return whether the memory allocation of the hashing context was successful */ static bool push_new_hash_depth(bool init) { cx_sha3_t *hash_ctx; cx_err_t error = CX_INTERNAL_ERROR; // allocate new hash context if ((hash_ctx = MEM_ALLOC_AND_ALIGN_TYPE(*hash_ctx)) == NULL) { return false; } if (init) { CX_CHECK(cx_keccak_init_no_throw(hash_ctx, 256)); } return true; end: return false; } /** * Go up (remove) a depth level. * * @return whether the pop was successful */ static bool path_depth_list_pop(void) { uint8_t hash[KECCAK256_HASH_BYTESIZE]; bool to_feed; if (path_struct == NULL) { return false; } if (path_struct->depth_count == 0) { return false; } path_struct->depth_count -= 1; to_feed = finalize_hash_depth(hash); if (path_struct->depth_count > 0) { if (to_feed) { feed_last_hash_depth(hash); } } else { switch (path_struct->root_type) { case ROOT_DOMAIN: memcpy(tmpCtx.messageSigningContext712.domainHash, hash, KECCAK256_HASH_BYTESIZE); break; case ROOT_MESSAGE: memcpy(tmpCtx.messageSigningContext712.messageHash, hash, KECCAK256_HASH_BYTESIZE); break; default: break; } } return true; } /** * Go down (add) an array depth level. * * @param[in] path_idx the index in the path list * @param[in] the number of elements contained in that depth * @return whether the push was successful */ static bool array_depth_list_push(uint8_t path_idx, uint8_t size) { s_array_depth *arr; if (path_struct == NULL) { apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED; return false; } if (path_struct->array_depth_count == MAX_ARRAY_DEPTH) { apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED; return false; } arr = &path_struct->array_depths[path_struct->array_depth_count]; arr->path_index = path_idx; arr->size = size; arr->index = 0; path_struct->array_depth_count += 1; return true; } /** * Go up (remove) an array depth level. * * @return whether the pop was successful */ static bool array_depth_list_pop(void) { uint8_t hash[KECCAK256_HASH_BYTESIZE]; if (path_struct == NULL) { return false; } if (path_struct->array_depth_count == 0) { return false; } finalize_hash_depth(hash); // return value not checked on purpose feed_last_hash_depth(hash); path_struct->array_depth_count -= 1; return true; } /** * Updates the path so that it doesn't point to a struct-type field, but rather * only to actual fields. * * @param[in] skip_if_array skip if path is already pointing at an array * @param[in] stop_at_array stop at the first downstream array * @return whether the path update worked or not */ static bool path_update(bool skip_if_array, bool stop_at_array) { uint8_t fields_count; const void *struct_ptr; const void *starting_field_ptr; const void *field_ptr; const char *typename; uint8_t typename_len; uint8_t hash[KECCAK256_HASH_BYTESIZE]; if (path_struct == NULL) { return false; } if ((starting_field_ptr = get_field(NULL)) == NULL) { return false; } field_ptr = starting_field_ptr; while (struct_field_type(field_ptr) == TYPE_CUSTOM) { // check if we meet one of the given conditions if (((field_ptr == starting_field_ptr) && skip_if_array) || ((field_ptr != starting_field_ptr) && stop_at_array)) { // only if it is the first iteration of that array depth if ((path_struct->array_depths[path_struct->array_depth_count - 1].index == 0) && struct_field_is_array(field_ptr)) { break; } } typename = get_struct_field_typename(field_ptr, &typename_len); if ((struct_ptr = get_structn(typename, typename_len)) == NULL) { return false; } if ((field_ptr = get_struct_fields_array(struct_ptr, &fields_count)) == NULL) { return false; } if (push_new_hash_depth(true) == false) { return false; } // The only times they are both at false is if we are traversing an empty array, // don't do a typehash in that case if ((skip_if_array != false) || (stop_at_array != false)) { // get the struct typehash if (type_hash(typename, typename_len, hash) == false) { return false; } feed_last_hash_depth(hash); } // TODO: Find a better way to show inner structs in verbose mode when it might be // an empty array of structs in which case we don't want to show it but the // size is only known later // ui_712_queue_struct_to_review(); path_depth_list_push(); } return true; } /** * Set a new struct as the path root type * * @param[in] struct_name the root struct name * @param[in] name_length the root struct name length * @return boolean indicating if it was successful or not */ bool path_set_root(const char *const struct_name, uint8_t name_length) { uint8_t hash[KECCAK256_HASH_BYTESIZE]; if (path_struct == NULL) { apdu_response_code = APDU_RESPONSE_INVALID_DATA; return false; } path_struct->root_struct = get_structn(struct_name, name_length); if (path_struct->root_struct == NULL) { PRINTF("Struct name not found ("); for (int i = 0; i < name_length; ++i) { PRINTF("%c", struct_name[i]); } PRINTF(")!\n"); apdu_response_code = APDU_RESPONSE_INVALID_DATA; return false; } if (push_new_hash_depth(true) == false) { return false; } if (type_hash(struct_name, name_length, hash) == false) { return false; } feed_last_hash_depth(hash); // // init depth, at 0 : empty path path_struct->depth_count = 0; path_depth_list_push(); // init array levels at 0 path_struct->array_depth_count = 0; if ((name_length == strlen(DOMAIN_STRUCT_NAME)) && (strncmp(struct_name, DOMAIN_STRUCT_NAME, name_length) == 0)) { path_struct->root_type = ROOT_DOMAIN; } else { path_struct->root_type = ROOT_MESSAGE; } struct_state = DEFINED; // because the first field could be a struct type path_update(true, true); return true; } /** * Checks the new array depth and adds it to the list * * @param[in] depth pointer to the array depth definition * @param[in] total_count number of array depth contained down to this array depth * @param[in] pidx path index * @param[in] size requested array depth size * @return whether the checks and add were successful or not */ static bool check_and_add_array_depth(const void *depth, uint8_t total_count, uint8_t pidx, uint8_t size) { uint8_t expected_size; uint8_t arr_idx; e_array_type expected_type; arr_idx = (total_count - path_struct->array_depth_count) - 1; // we skip index 0, since we already have it for (uint8_t idx = 1; idx < (arr_idx + 1); ++idx) { if ((depth = get_next_struct_field_array_lvl(depth)) == NULL) { return false; } } expected_type = struct_field_array_depth(depth, &expected_size); if ((expected_type == ARRAY_FIXED_SIZE) && (expected_size != size)) { apdu_response_code = APDU_RESPONSE_INVALID_DATA; PRINTF("Unexpected array depth size. (expected %d, got %d)\n", expected_size, size); return false; } // add it if (!array_depth_list_push(pidx, size)) { return false; } return true; } /** * Add a new array depth with a given size (number of elements). * * @param[in] data pointer to the number of elements * @param[in] length length of data * @return whether the add was successful or not */ bool path_new_array_depth(const uint8_t *const data, uint8_t length) { const void *field_ptr = NULL; const void *depth = NULL; uint8_t depth_count; uint8_t total_count = 0; uint8_t pidx; bool is_custom; uint8_t array_size; uint8_t array_depth_count_bak; cx_err_t error = CX_INTERNAL_ERROR; if (path_struct == NULL) { apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED; return false; } else if (length != 1) { apdu_response_code = APDU_RESPONSE_INVALID_DATA; return false; } array_size = *data; if (!path_update(false, array_size > 0)) { return false; } array_depth_count_bak = path_struct->array_depth_count; for (pidx = 0; pidx < path_struct->depth_count; ++pidx) { if ((field_ptr = get_nth_field(NULL, pidx + 1)) == NULL) { apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED; return false; } if (struct_field_is_array(field_ptr)) { if ((depth = get_struct_field_array_lvls_array(field_ptr, &depth_count)) == NULL) { apdu_response_code = APDU_RESPONSE_CONDITION_NOT_SATISFIED; return false; } total_count += depth_count; if (total_count > path_struct->array_depth_count) { if (!check_and_add_array_depth(depth, total_count, pidx, array_size)) { return false; } break; } } } if (pidx == path_struct->depth_count) { apdu_response_code = APDU_RESPONSE_INVALID_DATA; PRINTF("Did not find a matching array type.\n"); return false; } is_custom = struct_field_type(field_ptr) == TYPE_CUSTOM; if (push_new_hash_depth(!is_custom) == false) { return false; } if (is_custom) { cx_sha3_t *hash_ctx = get_last_hash_ctx(); cx_sha3_t *old_ctx = hash_ctx - 1; if (array_size > 0) { memcpy(hash_ctx, old_ctx, sizeof(*old_ctx)); } else { CX_CHECK(cx_keccak_init_no_throw(hash_ctx, 256)); } CX_CHECK(cx_keccak_init_no_throw(old_ctx, 256)); } if (array_size == 0) { do { path_advance(false); } while (path_struct->array_depth_count > array_depth_count_bak); } return true; end: return false; } /** * Advance within the struct that contains the field the path points to. * * @return whether the end of the struct has been reached. */ static bool path_advance_in_struct(void) { bool end_reached = true; uint8_t *depth = &path_struct->depths[path_struct->depth_count - 1]; uint8_t fields_count; if (path_struct == NULL) { return false; } if ((get_field(&fields_count)) == NULL) { return false; } if (path_struct->depth_count > 0) { *depth += 1; end_reached = (*depth == fields_count); } if (end_reached) { path_depth_list_pop(); } return end_reached; } /** * Advance within the array levels of the current field the path points to. * * @return whether the end of the array levels has been reached. */ static bool path_advance_in_array(void) { bool end_reached; s_array_depth *arr_depth; if (path_struct == NULL) { return false; } do { end_reached = false; arr_depth = &path_struct->array_depths[path_struct->array_depth_count - 1]; if ((path_struct->array_depth_count > 0) && (arr_depth->path_index == (path_struct->depth_count - 1))) { arr_depth->index += 1; if (arr_depth->index == arr_depth->size) { array_depth_list_pop(); end_reached = true; } else { return false; } } } while (end_reached); return true; } /** * Updates the path to point to the next field in order (DFS). * * @return whether the advancement was successful or not */ bool path_advance(bool array_check) { bool end_reached; do { if (path_advance_in_array()) { end_reached = path_advance_in_struct(); } else { end_reached = false; } } while (end_reached); return path_update(array_check, array_check); } /** * Get root structure type from path (domain or message) * * @return enum representing root type */ e_root_type path_get_root_type(void) { if (path_struct == NULL) { return ROOT_DOMAIN; } return path_struct->root_type; } /** * Get root structure from path * * @return pointer to the root structure definition */ const void *path_get_root(void) { if (path_struct == NULL) { return NULL; } return path_struct->root_struct; } /** * Get the current amount of depth * * @return depth count */ uint8_t path_get_depth_count(void) { if (path_struct == NULL) { return 0; } return path_struct->depth_count; } /** * Generate a unique checksum out of the current path * * Goes over the fields of the \ref path_struct with a few exceptions : we skip the root_type since * we already go over root_struct, and in array_depths we only go over path_index since it would * otherwise generate a different CRC for different fields which are targeted by the same filtering * path. * * @return CRC-32 checksum */ uint32_t get_path_crc(void) { uint32_t value = CX_CRC32_INIT; value = cx_crc32_update(value, &path_struct->root_struct, sizeof(path_struct->root_struct)); value = cx_crc32_update(value, &path_struct->depth_count, sizeof(path_struct->depth_count)); value = cx_crc32_update(value, path_struct->depths, sizeof(path_struct->depths[0]) * path_struct->depth_count); value = cx_crc32_update(value, &path_struct->array_depth_count, sizeof(path_struct->array_depth_count)); for (int i = 0; i < path_struct->array_depth_count; ++i) { value = cx_crc32_update(value, &path_struct->array_depths[i].path_index, sizeof(path_struct->array_depths[i].path_index)); } return value; } /** * Initialize the path context with its indexes in memory and sets it with a depth of 0. * * @return whether the memory allocation were successful. */ bool path_init(void) { if (path_struct == NULL) { if ((path_struct = MEM_ALLOC_AND_ALIGN_TYPE(*path_struct)) == NULL) { apdu_response_code = APDU_RESPONSE_INSUFFICIENT_MEMORY; } else { path_struct->depth_count = 0; } } return path_struct != NULL; } /** * De-initialize the path context */ void path_deinit(void) { path_struct = NULL; } #endif // HAVE_EIP712_FULL_SUPPORT