Embedded SDK
Embedded SDK
Loading...
Searching...
No Matches
identity_register.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
30/* Includes ------------------------------------------------------------------*/
31#include <string.h>
32#include "os_utils.h"
33#include "os_apdu.h"
34#include "status_words.h"
35#include "tlv_library.h"
36#include "buffer.h"
37#include "bip32.h"
38#include "address_book.h"
40#include "address_book_crypto.h"
41#include "address_book_common.h"
42#include "identity.h"
43#include "io.h"
44#include "nbgl_use_case.h"
45
46#if defined(HAVE_ADDRESS_BOOK)
47
48/* Private defines------------------------------------------------------------*/
49#define STRUCT_VERSION 0x01
50
51/* Private enumerations ------------------------------------------------------*/
52
53/* Private types, structures, unions -----------------------------------------*/
54
55typedef struct {
56 s_register_state_t *state;
57 TLV_reception_t received_tags;
58} s_identity_ctx;
59
60/* Private macros-------------------------------------------------------------*/
61#define REGISTER_IDENTITY_TAGS(X) \
62 X(0x01, TAG_STRUCTURE_TYPE, handle_struct_type, ENFORCE_UNIQUE_TAG) \
63 X(0x02, TAG_STRUCTURE_VERSION, handle_struct_version, ENFORCE_UNIQUE_TAG) \
64 X(0xf0, TAG_CONTACT_NAME, handle_contact_name, ENFORCE_UNIQUE_TAG) \
65 X(0xf1, TAG_SCOPE, handle_scope, ENFORCE_UNIQUE_TAG) \
66 X(0xf2, TAG_ACCOUNT_IDENTIFIER, handle_identifier, ENFORCE_UNIQUE_TAG) \
67 X(0x21, TAG_DERIVATION_PATH, handle_derivation_path, ENFORCE_UNIQUE_TAG) \
68 X(0x23, TAG_CHAIN_ID, handle_chain_id, ENFORCE_UNIQUE_TAG) \
69 X(0x51, TAG_BLOCKCHAIN_FAMILY, handle_blockchain_family, ENFORCE_UNIQUE_TAG) \
70 X(0xf6, TAG_GROUP_HANDLE, handle_group_handle, ENFORCE_UNIQUE_TAG) \
71 X(0x29, TAG_HMAC_PROOF, handle_hmac_proof, ENFORCE_UNIQUE_TAG)
72
73/* Private variables ---------------------------------------------------------*/
74
75/* Private functions ---------------------------------------------------------*/
76
84static bool handle_struct_type(const tlv_data_t *data, s_identity_ctx *context)
85{
86 UNUSED(context);
87 if (!tlv_enforce_u8_value(data, TYPE_REGISTER_IDENTITY)) {
88 PRINTF("[Register Identity] Invalid STRUCTURE_TYPE value\n");
89 return false;
90 }
91 return true;
92}
93
101static bool handle_struct_version(const tlv_data_t *data, s_identity_ctx *context)
102{
103 UNUSED(context);
104 if (!tlv_enforce_u8_value(data, STRUCT_VERSION)) {
105 PRINTF("[Register Identity] Invalid STRUCTURE_VERSION value\n");
106 return false;
107 }
108 return true;
109}
110
118static bool handle_contact_name(const tlv_data_t *data, s_identity_ctx *context)
119{
120 if (!address_book_handle_printable_string(data,
121 context->state->identity.contact_name,
122 sizeof(context->state->identity.contact_name))) {
123 PRINTF("CONTACT_NAME: failed to parse\n");
124 return false;
125 }
126 return true;
127}
128
136static bool handle_scope(const tlv_data_t *data, s_identity_ctx *context)
137{
138 if (!address_book_handle_printable_string(
139 data, context->state->identity.scope, sizeof(context->state->identity.scope))) {
140 PRINTF("SCOPE: failed to parse\n");
141 return false;
142 }
143 return true;
144}
145
153static bool handle_identifier(const tlv_data_t *data, s_identity_ctx *context)
154{
155 buffer_t buf = {0};
156 if (!get_buffer_from_tlv_data(data, &buf, 1, IDENTIFIER_MAX_LENGTH)) {
157 PRINTF("IDENTIFIER: failed to extract\n");
158 return false;
159 }
160 memmove(context->state->identity.identifier, buf.ptr, buf.size);
161 context->state->identity.identifier_len = (uint8_t) buf.size;
162 return true;
163}
164
172static bool handle_derivation_path(const tlv_data_t *data, s_identity_ctx *context)
173{
174 return address_book_handle_derivation_path(data, &context->state->identity.bip32_path);
175}
176
184static bool handle_chain_id(const tlv_data_t *data, s_identity_ctx *context)
185{
186 return address_book_handle_chain_id(data, &context->state->identity.chain_id);
187}
188
196static bool handle_blockchain_family(const tlv_data_t *data, s_identity_ctx *context)
197{
198 return address_book_handle_blockchain_family(data, &context->state->identity.blockchain_family);
199}
200
208static bool handle_group_handle(const tlv_data_t *data, s_identity_ctx *context)
209{
210 buffer_t buf = {0};
211 if (!get_buffer_from_tlv_data(data, &buf, GROUP_HANDLE_SIZE, GROUP_HANDLE_SIZE)) {
212 PRINTF("[Register Identity] GROUP_HANDLE: invalid length (expected %d bytes)\n",
213 GROUP_HANDLE_SIZE);
214 return false;
215 }
216 memmove(context->state->group_handle, buf.ptr, GROUP_HANDLE_SIZE);
217 return true;
218}
219
227static bool handle_hmac_proof(const tlv_data_t *data, s_identity_ctx *context)
228{
229 buffer_t buf = {0};
230 if (!get_buffer_from_tlv_data(data, &buf, CX_SHA256_SIZE, CX_SHA256_SIZE)) {
231 PRINTF("[Register Identity] HMAC_PROOF: invalid length (expected %d bytes)\n",
232 CX_SHA256_SIZE);
233 return false;
234 }
235 memmove(context->state->hmac_proof, buf.ptr, CX_SHA256_SIZE);
236 return true;
237}
238
239DEFINE_TLV_PARSER(REGISTER_IDENTITY_TAGS, NULL, identity_tlv_parser)
240
241
247static bool verify_fields(const s_identity_ctx *context)
248{
249 bool result = TLV_CHECK_RECEIVED_TAGS(context->received_tags,
250 TAG_STRUCTURE_TYPE,
251 TAG_STRUCTURE_VERSION,
252 TAG_CONTACT_NAME,
253 TAG_SCOPE,
254 TAG_ACCOUNT_IDENTIFIER,
255 TAG_DERIVATION_PATH,
256 TAG_BLOCKCHAIN_FAMILY);
257 if (!result) {
258 PRINTF("Missing mandatory fields in Register Identity descriptor!\n");
259 return false;
260 }
261 if (context->state->identity.blockchain_family == FAMILY_ETHEREUM) {
262 result = TLV_CHECK_RECEIVED_TAGS(context->received_tags, TAG_CHAIN_ID);
263 if (!result) {
264 PRINTF("Missing CHAIN_ID for Ethereum family in Register Identity descriptor!\n");
265 return false;
266 }
267 }
268 // GROUP_HANDLE and HMAC_PROOF are optional, but must be provided together
269 bool has_group_handle = TLV_CHECK_RECEIVED_TAGS(context->received_tags, TAG_GROUP_HANDLE);
270 bool has_hmac_proof = TLV_CHECK_RECEIVED_TAGS(context->received_tags, TAG_HMAC_PROOF);
271 if (has_group_handle != has_hmac_proof) {
272 PRINTF("GROUP_HANDLE and HMAC_PROOF must be provided together!\n");
273 return false;
274 }
275 return true;
276}
277
284static void print_payload(const s_identity_ctx *context)
285{
286 UNUSED(context);
287 char out[50] = {0};
288
289 PRINTF("****************************************************************************\n");
290 PRINTF("[Register Identity] - Retrieved Descriptor:\n");
291 PRINTF("[Register Identity] - Contact name: %s\n",
292 context->state->identity.contact_name);
293 if (context->state->identity.scope[0] != '\0') {
294 PRINTF("[Register Identity] - Contact scope: %s\n",
295 context->state->identity.scope);
296 }
297 if (bip32_path_format_simple(&context->state->identity.bip32_path, out, sizeof(out))) {
298 PRINTF("[Register Identity] - Derivation path[%d]: %s\n",
299 context->state->identity.bip32_path.length,
300 out);
301 }
302 else {
303 PRINTF("[Register Identity] - Derivation path length[%d] (failed to format)\n",
304 context->state->identity.bip32_path.length);
305 }
306 PRINTF("[Register Identity] - Blockchain family: %s\n",
307 FAMILY_AS_STR(context->state->identity.blockchain_family));
308 if (context->state->identity.blockchain_family == FAMILY_ETHEREUM) {
309 PRINTF("[Register Identity] - Chain ID: %llu\n",
310 context->state->identity.chain_id);
311 }
312}
313
327static bool build_and_send_response(void)
328{
329 uint8_t group_handle[GROUP_HANDLE_SIZE] = {0};
330 uint8_t hmac_proof[CX_SHA256_SIZE] = {0};
331 uint8_t hmac_rest[CX_SHA256_SIZE] = {0};
332 bool ok = false;
333
334 if (g_ab_payload.reg.active) {
335 // group_handle and HMAC_PROOF were already verified before the UI — use the
336 // pre-extracted GID directly and only compute the new HMAC_REST.
337 if (!address_book_compute_hmac_rest(&g_ab_payload.reg.identity.bip32_path,
338 g_ab_payload.reg.gid,
339 g_ab_payload.reg.identity.scope,
340 g_ab_payload.reg.identity.identifier,
341 g_ab_payload.reg.identity.identifier_len,
342 g_ab_payload.reg.identity.blockchain_family,
343 g_ab_payload.reg.identity.chain_id,
344 hmac_rest)) {
345 PRINTF("[Register Identity] Error: Failed to compute HMAC_REST for new address\n");
346 goto end;
347 }
348 // Echo back the verified group_handle and hmac_proof; only hmac_rest is new
349 memmove(group_handle, g_ab_payload.reg.group_handle, GROUP_HANDLE_SIZE);
350 memmove(hmac_proof, g_ab_payload.reg.hmac_proof, CX_SHA256_SIZE);
351 }
352 else {
353 if (!address_book_generate_group_handle(&g_ab_payload.reg.identity.bip32_path,
354 group_handle)) {
355 PRINTF("[Register Identity] Error: Failed to generate group handle\n");
356 goto end;
357 }
358 // group_handle layout: gid(32) | MAC(K_group, gid)(32) — use only the GID prefix here
359 const uint8_t *gid = group_handle;
360 if (!address_book_compute_hmac_proof(&g_ab_payload.reg.identity.bip32_path,
361 gid,
362 g_ab_payload.reg.identity.contact_name,
363 hmac_proof)) {
364 PRINTF("[Register Identity] Error: Failed to compute HMAC_PROOF\n");
365 goto end;
366 }
367 if (!address_book_compute_hmac_rest(&g_ab_payload.reg.identity.bip32_path,
368 gid,
369 g_ab_payload.reg.identity.scope,
370 g_ab_payload.reg.identity.identifier,
371 g_ab_payload.reg.identity.identifier_len,
372 g_ab_payload.reg.identity.blockchain_family,
373 g_ab_payload.reg.identity.chain_id,
374 hmac_rest)) {
375 PRINTF("[Register Identity] Error: Failed to compute HMAC_REST\n");
376 goto end;
377 }
378 }
379 ok = address_book_send_register_identity_response(group_handle, hmac_proof, hmac_rest);
380
381end:
382 explicit_bzero(group_handle, sizeof(group_handle));
383 explicit_bzero(hmac_proof, sizeof(hmac_proof));
384 explicit_bzero(hmac_rest, sizeof(hmac_rest));
385 return ok;
386}
387
393static void review_choice(bool confirm)
394{
395 if (confirm) {
396 bool ok = build_and_send_response();
397 if (!ok) {
398 PRINTF("[Register Identity] Error: Failed to build and send HMAC proof\n");
399 }
400 address_book_finalize_review(ok,
401 "Saved to your Contacts",
402 "Error during registration",
403 finalize_ui_register_identity);
404 }
405 else {
406 address_book_handle_review_rejected(finalize_ui_register_identity);
407 }
408}
409
413static void ui_display(void)
414{
415 memset(&g_ab_ui.list, 0, sizeof(g_ab_ui.list));
416 g_ab_ui.list.nbPairs = 4; // name + scope + identifier + network
417 g_ab_ui.list.callback = get_register_identity_tagValue;
418 address_book_display_review(&LARGE_ADDRESS_BOOK_ICON,
419 "Review contact details",
420#ifdef SCREEN_SIZE_WALLET
421 "Confirm contact details?",
422#else
423 "Confirm contact details",
424#endif
425 review_choice);
426}
427
428/* Exported functions --------------------------------------------------------*/
429
437bolos_err_t register_identity(uint8_t *buffer_in, size_t buffer_in_length)
438{
439 const buffer_t payload = {.ptr = buffer_in, .size = buffer_in_length};
440 s_identity_ctx ctx = {0};
441
442 // Init the structure
443 memset(&g_ab_payload.reg, 0, sizeof(g_ab_payload.reg));
444 ctx.state = &g_ab_payload.reg;
445
446 // Parse using SDK TLV parser — handlers write directly into g_ab_payload.reg via ctx.state
447 if (!identity_tlv_parser(&payload, &ctx, &ctx.received_tags)) {
448 PRINTF("[Register Identity] Failed to parse TLV payload\n");
449 return SWO_INCORRECT_DATA;
450 }
451 if (!verify_fields(&ctx)) {
452 return SWO_INCORRECT_DATA;
453 }
454 print_payload(&ctx);
455
456 // Validate group-handle integrity and caller ownership before showing the UI.
457 // These are input checks (wallet-supplied data) — failing here returns an error
458 // immediately; only internal crypto operations remain post-confirm.
459 if (TLV_CHECK_RECEIVED_TAGS(ctx.received_tags, TAG_GROUP_HANDLE)) {
460 g_ab_payload.reg.active = true;
461 if (!address_book_verify_group_handle(&g_ab_payload.reg.identity.bip32_path,
462 g_ab_payload.reg.group_handle,
463 g_ab_payload.reg.gid)) {
464 PRINTF("[Register Identity] Error: Group handle verification failed\n");
465 return SWO_SECURITY_CONDITION_NOT_SATISFIED;
466 }
467 if (!address_book_verify_hmac_proof(&g_ab_payload.reg.identity.bip32_path,
468 g_ab_payload.reg.gid,
469 g_ab_payload.reg.identity.contact_name,
470 g_ab_payload.reg.hmac_proof)) {
471 PRINTF("[Register Identity] Error: HMAC_PROOF verification failed\n");
472 return SWO_SECURITY_CONDITION_NOT_SATISFIED;
473 }
474 }
475
476 // Check the Identity validity according to the Coin application logic
477 if (!handle_check_register_identity(&g_ab_payload.reg.identity)) {
478 PRINTF("[Register Identity] Error: Identity rejected by coin application\n");
479 return SWO_WRONG_PARAMETER_VALUE;
480 }
481
482 // Display confirmation UI
483 ui_display();
484 return SWO_NO_RESPONSE;
485}
486
487#endif // HAVE_ADDRESS_BOOK
@ FAMILY_ETHEREUM
#define FAMILY_AS_STR(x)
bool bip32_path_format_simple(path_bip32_t *bip32, char *out, size_t out_len)
Format a BIP32 path as a string.
Definition bip32.c:92
Register / Edit Contact Name / Edit Scope / Edit Identifier.
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