Embedded SDK
Embedded SDK
Loading...
Searching...
No Matches
identity_edit_scope.c
Go to the documentation of this file.
1/*****************************************************************************
2 * (c) 2026 Ledger SAS.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *****************************************************************************/
16
39/* Includes ------------------------------------------------------------------*/
40#include <string.h>
41#include "os_apdu.h"
42#include "status_words.h"
43#include "tlv_library.h"
44#include "buffer.h"
45#include "bip32.h"
46#include "address_book.h"
48#include "address_book_crypto.h"
49#include "address_book_common.h"
50#include "identity.h"
51#include "io.h"
52#include "nbgl_use_case.h"
53
54#if defined(HAVE_ADDRESS_BOOK)
55
56/* Private defines------------------------------------------------------------*/
57#define STRUCT_VERSION 0x01
58
59/* Private types, structures, unions -----------------------------------------*/
60typedef struct {
61 edit_scope_t *edit;
62 TLV_reception_t received_tags;
63 uint8_t hmac_proof[CX_SHA256_SIZE];
64 uint8_t hmac_rest[CX_SHA256_SIZE];
65 uint8_t group_handle[GROUP_HANDLE_SIZE];
66} s_edit_scope_ctx;
67
68/* Private macros-------------------------------------------------------------*/
69#define EDIT_SCOPE_TAGS(X) \
70 X(0x01, TAG_STRUCTURE_TYPE, handle_struct_type, ENFORCE_UNIQUE_TAG) \
71 X(0x02, TAG_STRUCTURE_VERSION, handle_struct_version, ENFORCE_UNIQUE_TAG) \
72 X(0xf0, TAG_CONTACT_NAME, handle_contact_name, ENFORCE_UNIQUE_TAG) \
73 X(0xf1, TAG_SCOPE, handle_scope, ENFORCE_UNIQUE_TAG) \
74 X(0xf2, TAG_ACCOUNT_IDENTIFIER, handle_identifier, ENFORCE_UNIQUE_TAG) \
75 X(0xf5, TAG_PREVIOUS_SCOPE, handle_previous_scope, ENFORCE_UNIQUE_TAG) \
76 X(0xf6, TAG_GROUP_HANDLE, handle_group_handle, ENFORCE_UNIQUE_TAG) \
77 X(0x23, TAG_CHAIN_ID, handle_chain_id, ENFORCE_UNIQUE_TAG) \
78 X(0x29, TAG_HMAC_PROOF, handle_hmac_proof, ENFORCE_UNIQUE_TAG) \
79 X(0xf7, TAG_HMAC_REST, handle_hmac_rest, ENFORCE_UNIQUE_TAG) \
80 X(0x51, TAG_BLOCKCHAIN_FAMILY, handle_blockchain_family, ENFORCE_UNIQUE_TAG)
81
82/* Private variables ---------------------------------------------------------*/
83
84/* Private functions ---------------------------------------------------------*/
85
93static bool handle_struct_type(const tlv_data_t *data, s_edit_scope_ctx *context)
94{
95 UNUSED(context);
96 if (!tlv_enforce_u8_value(data, TYPE_EDIT_SCOPE)) {
97 PRINTF("[Edit Scope] Invalid STRUCTURE_TYPE value\n");
98 return false;
99 }
100 return true;
101}
102
110static bool handle_struct_version(const tlv_data_t *data, s_edit_scope_ctx *context)
111{
112 UNUSED(context);
113 if (!tlv_enforce_u8_value(data, STRUCT_VERSION)) {
114 PRINTF("[Edit Scope] Invalid STRUCTURE_VERSION value\n");
115 return false;
116 }
117 return true;
118}
119
127static bool handle_contact_name(const tlv_data_t *data, s_edit_scope_ctx *context)
128{
129 if (!address_book_handle_printable_string(data,
130 context->edit->identity.contact_name,
131 sizeof(context->edit->identity.contact_name))) {
132 PRINTF("[Edit Scope] CONTACT_NAME: failed to parse\n");
133 return false;
134 }
135 return true;
136}
137
147static bool handle_scope(const tlv_data_t *data, s_edit_scope_ctx *context)
148{
149 if (!address_book_handle_printable_string(
150 data, context->edit->identity.scope, sizeof(context->edit->identity.scope))) {
151 PRINTF("[Edit Scope] SCOPE: failed to parse\n");
152 return false;
153 }
154 return true;
155}
156
164static bool handle_identifier(const tlv_data_t *data, s_edit_scope_ctx *context)
165{
166 buffer_t buf = {0};
167 if (!get_buffer_from_tlv_data(data, &buf, 1, IDENTIFIER_MAX_LENGTH)) {
168 PRINTF("[Edit Scope] IDENTIFIER: failed to extract\n");
169 return false;
170 }
171 memmove(context->edit->identity.identifier, buf.ptr, buf.size);
172 context->edit->identity.identifier_len = (uint8_t) buf.size;
173 return true;
174}
175
185static bool handle_previous_scope(const tlv_data_t *data, s_edit_scope_ctx *context)
186{
187 if (!address_book_handle_printable_string(
188 data, context->edit->old_scope, sizeof(context->edit->old_scope))) {
189 PRINTF("[Edit Scope] PREVIOUS_SCOPE: failed to parse\n");
190 return false;
191 }
192 return true;
193}
194
202static bool handle_group_handle(const tlv_data_t *data, s_edit_scope_ctx *context)
203{
204 buffer_t buf = {0};
205 if (!get_buffer_from_tlv_data(data, &buf, GROUP_HANDLE_SIZE, GROUP_HANDLE_SIZE)) {
206 PRINTF("[Edit Scope] GROUP_HANDLE: invalid length (expected %d bytes)\n",
207 GROUP_HANDLE_SIZE);
208 return false;
209 }
210 memmove(context->group_handle, buf.ptr, GROUP_HANDLE_SIZE);
211 return true;
212}
213
221static bool handle_chain_id(const tlv_data_t *data, s_edit_scope_ctx *context)
222{
223 return address_book_handle_chain_id(data, &context->edit->identity.chain_id);
224}
225
233static bool handle_blockchain_family(const tlv_data_t *data, s_edit_scope_ctx *context)
234{
235 return address_book_handle_blockchain_family(data, &context->edit->identity.blockchain_family);
236}
237
245static bool handle_hmac_proof(const tlv_data_t *data, s_edit_scope_ctx *context)
246{
247 buffer_t buf = {0};
248 if (!get_buffer_from_tlv_data(data, &buf, CX_SHA256_SIZE, CX_SHA256_SIZE)) {
249 PRINTF("[Edit Scope] HMAC_PROOF: invalid length (expected %d bytes)\n", CX_SHA256_SIZE);
250 return false;
251 }
252 memmove(context->hmac_proof, buf.ptr, CX_SHA256_SIZE);
253 return true;
254}
255
263static bool handle_hmac_rest(const tlv_data_t *data, s_edit_scope_ctx *context)
264{
265 buffer_t buf = {0};
266 if (!get_buffer_from_tlv_data(data, &buf, CX_SHA256_SIZE, CX_SHA256_SIZE)) {
267 PRINTF("[Edit Scope] HMAC_REST: invalid length (expected %d bytes)\n", CX_SHA256_SIZE);
268 return false;
269 }
270 memmove(context->hmac_rest, buf.ptr, CX_SHA256_SIZE);
271 return true;
272}
273
274DEFINE_TLV_PARSER(EDIT_SCOPE_TAGS, NULL, edit_scope_tlv_parser)
275
276
282static bool verify_fields(const s_edit_scope_ctx *context)
283{
284 bool result = TLV_CHECK_RECEIVED_TAGS(context->received_tags,
285 TAG_STRUCTURE_TYPE,
286 TAG_STRUCTURE_VERSION,
287 TAG_CONTACT_NAME,
288 TAG_SCOPE,
289 TAG_ACCOUNT_IDENTIFIER,
290 TAG_PREVIOUS_SCOPE,
291 TAG_GROUP_HANDLE,
292 TAG_BLOCKCHAIN_FAMILY,
293 TAG_HMAC_PROOF,
294 TAG_HMAC_REST);
295 if (!result) {
296 PRINTF("[Edit Scope] Missing mandatory fields!\n");
297 return false;
298 }
299 if (context->edit->identity.blockchain_family == FAMILY_ETHEREUM) {
300 result = TLV_CHECK_RECEIVED_TAGS(context->received_tags, TAG_CHAIN_ID);
301 if (!result) {
302 PRINTF("[Edit Scope] Missing CHAIN_ID for Ethereum!\n");
303 return false;
304 }
305 }
306 return true;
307}
308
315static void print_payload(const s_edit_scope_ctx *context)
316{
317 UNUSED(context);
318 PRINTF("****************************************************************************\n");
319 PRINTF("[Edit Scope] - Retrieved Descriptor:\n");
320 PRINTF("[Edit Scope] - Contact name: %s\n", context->edit->identity.contact_name);
321 PRINTF("[Edit Scope] - Old scope: %s\n", context->edit->old_scope);
322 PRINTF("[Edit Scope] - New scope: %s\n", context->edit->identity.scope);
323 PRINTF("[Edit Scope] - Blockchain family: %s\n",
324 FAMILY_AS_STR(context->edit->identity.blockchain_family));
325 if (context->edit->identity.blockchain_family == FAMILY_ETHEREUM) {
326 PRINTF("[Edit Scope] - Chain ID: %llu\n", context->edit->identity.chain_id);
327 }
328}
329
338static bool build_and_send_response(void)
339{
340 uint8_t hmac_rest[CX_SHA256_SIZE] = {0};
341
342 if (!address_book_compute_hmac_rest(g_ab_payload.edit_scope.identity.gid,
343 g_ab_payload.edit_scope.identity.scope,
344 g_ab_payload.edit_scope.identity.identifier,
345 g_ab_payload.edit_scope.identity.identifier_len,
346 g_ab_payload.edit_scope.identity.blockchain_family,
347 g_ab_payload.edit_scope.identity.chain_id,
348 hmac_rest)) {
349 PRINTF("[Edit Scope] Error: Failed to compute new HMAC_REST\n");
350 explicit_bzero(hmac_rest, sizeof(hmac_rest));
351 return false;
352 }
353 bool ok = address_book_send_hmac_proof(TYPE_EDIT_SCOPE, hmac_rest);
354 explicit_bzero(hmac_rest, sizeof(hmac_rest));
355 return ok;
356}
357
363static void review_choice(bool confirm)
364{
365 if (confirm) {
366 bool ok = build_and_send_response();
367 const char *successMsg
368 = (g_ab_payload.edit_scope.identity.blockchain_family == FAMILY_BITCOIN)
369 ? "Scope changed"
370 : "Address name changed";
371 if (ok) {
372 on_edit_scope_applied(&g_ab_payload.edit_scope);
373 }
374 else {
375 PRINTF("[Edit Scope] Error: Failed to build and send HMAC proof\n");
376 }
377 address_book_finalize_review(ok, successMsg, "Error during update", finalize_ui_edit_scope);
378 }
379 else {
380 address_book_handle_review_rejected(finalize_ui_edit_scope);
381 }
382}
383
387static void ui_display(void)
388{
389 uint8_t nbPairs = 0;
390 bool isBitcoin = (g_ab_payload.edit_scope.identity.blockchain_family == FAMILY_BITCOIN);
391
392 memset(&g_ab_ui.list, 0, sizeof(g_ab_ui.list));
393 memset(g_ab_ui.pairs, 0, sizeof(g_ab_ui.pairs));
394 g_ab_ui.pairs[nbPairs].item = "Contact name";
395 g_ab_ui.pairs[nbPairs].value = g_ab_payload.edit_scope.identity.contact_name;
396 nbPairs++;
397 g_ab_ui.pairs[nbPairs].item = isBitcoin ? "Old scope" : "Old address name";
398 g_ab_ui.pairs[nbPairs].value = g_ab_payload.edit_scope.old_scope;
399 nbPairs++;
400 g_ab_ui.pairs[nbPairs].item = isBitcoin ? "New scope" : "New address name";
401 g_ab_ui.pairs[nbPairs].value = g_ab_payload.edit_scope.identity.scope;
402 nbPairs++;
403 g_ab_ui.list.pairs = g_ab_ui.pairs;
404 g_ab_ui.list.nbPairs = nbPairs;
405 address_book_display_review(&LARGE_ADDRESS_BOOK_ICON,
406 "Review change to contact details",
407#ifdef SCREEN_SIZE_WALLET
408 "Confirm change?",
409#else
410 "Confirm change",
411#endif
412 review_choice);
413}
414
415/* Exported functions --------------------------------------------------------*/
416
424bolos_err_t edit_scope(uint8_t *buffer_in, size_t buffer_in_length)
425{
426 const buffer_t payload = {.ptr = buffer_in, .size = buffer_in_length};
427 s_edit_scope_ctx ctx = {0};
428
429 // Init the structure
430 ctx.edit = &g_ab_payload.edit_scope;
431 memset(&g_ab_payload.edit_scope, 0, sizeof(g_ab_payload.edit_scope));
432
433 // Parse using SDK TLV parser
434 if (!edit_scope_tlv_parser(&payload, &ctx, &ctx.received_tags)) {
435 PRINTF("[Edit Scope] TLV parsing failed\n");
436 return SWO_INCORRECT_DATA;
437 }
438 if (!verify_fields(&ctx)) {
439 return SWO_INCORRECT_DATA;
440 }
441 print_payload(&ctx);
442
443 // Verify the group handle and extract the gid
444 if (!address_book_verify_group_handle(ctx.group_handle, g_ab_payload.edit_scope.identity.gid)) {
445 PRINTF("[Edit Scope] Group handle verification failed\n");
446 return SWO_SECURITY_CONDITION_NOT_SATISFIED;
447 }
448
449 // Verify that the wallet holds a valid HMAC_PROOF for the contact name
450 if (!address_book_verify_hmac_proof(g_ab_payload.edit_scope.identity.gid,
451 g_ab_payload.edit_scope.identity.contact_name,
452 ctx.hmac_proof)) {
453 PRINTF("[Edit Scope] HMAC_PROOF verification failed\n");
454 return SWO_SECURITY_CONDITION_NOT_SATISFIED;
455 }
456
457 // Verify that the wallet holds a valid HMAC_REST for the previous scope
458 if (!address_book_verify_hmac_rest(g_ab_payload.edit_scope.identity.gid,
459 g_ab_payload.edit_scope.old_scope,
460 g_ab_payload.edit_scope.identity.identifier,
461 g_ab_payload.edit_scope.identity.identifier_len,
462 g_ab_payload.edit_scope.identity.blockchain_family,
463 g_ab_payload.edit_scope.identity.chain_id,
464 ctx.hmac_rest)) {
465 PRINTF("[Edit Scope] HMAC_REST verification failed\n");
466 return SWO_SECURITY_CONDITION_NOT_SATISFIED;
467 }
468
469 // Display confirmation UI
470 ui_display();
471 return SWO_NO_RESPONSE;
472}
473
474#endif // HAVE_ADDRESS_BOOK
@ FAMILY_BITCOIN
@ FAMILY_ETHEREUM
#define FAMILY_AS_STR(x)
Register / Edit Contact Name / Edit Scope / Edit Identifier.
#define LARGE_ADDRESS_BOOK_ICON
Definition nbgl_icons.h:228
API of the Advanced BOLOS Graphical Library, for typical application use-cases.
uint8_t * ptr
Definition buffer.h:22
size_t size
Pointer to byte buffer.
Definition buffer.h:23
bool tlv_enforce_u8_value(const tlv_data_t *data, uint8_t expected_value)
Enforce that a TLV data contains the expected uint8 value.
Definition tlv_library.c:38
bool get_buffer_from_tlv_data(const tlv_data_t *data, buffer_t *out, uint16_t min_size, uint16_t max_size)
Extract a sized buffer from TLV data (zero-copy).
#define DEFINE_TLV_PARSER(TAG_LIST, COMMON_HANDLER, PARSE_FUNCTION_NAME)
Creates a parser function for a given TLV use case.
#define TLV_CHECK_RECEIVED_TAGS(received,...)
static bool handle_chain_id(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)
static bool handle_blockchain_family(const tlv_data_t *data, tlv_extracted_t *tlv_extracted)